Pipeline
Foundation Pipeline passes a value through an ordered chain of pipes. Each pipe can transform the value, perform a check, stop execution, or call the next pipe. The implementation is based on Laravel’s Pipeline pattern and uses the Foundation container to resolve class-based pipes.
Pipelines are useful when one operation has several independent steps whose order should remain visible, such as normalizing input, validating business rules, enriching data, and persisting the final result.
Installation
Section titled “Installation”Install the split package:
Prepare the application
Section titled “Prepare the application”Pipeline has no service provider or configuration file. It uses the shared container when class names are supplied as pipes:
Configuration
Section titled “Configuration”Configure a pipeline in a provider
Section titled “Configure a pipeline in a provider”Define the ordered pipe list in the feature provider, then use a contextual binding to select that configured pipeline for its consumer. This keeps workflow composition out of the class that runs it.
In src/Catalog/Provider.php:
In src/Catalog/Product_Importer.php, the consumer uses the pipeline it receives without knowing which pipes compose it:
Register each distinct workflow under its own container identifier and contextually give consumers the one they need. Class-name pipes are resolved through the container, so their own dependencies remain injectable.
Create a transforming pipe
Section titled “Create a transforming pipe”Create src/Catalog/Normalize_Product.php for the first pipe. A pipe receives the current value and a $next closure; pass the transformed value to $next to continue:
The first configured pipe runs first. The destination passed to then() runs only after every pipe calls $next.
Reject invalid input
Section titled “Reject invalid input”Create src/Catalog/Require_Product_Sku.php for the validation pipe. Throw when the operation cannot continue; Pipeline rethrows exceptions from pipes and the destination unchanged:
Catch the exception at the application boundary that can report, retry, or convert the failure. Avoid swallowing it inside the pipeline unless stopping is an expected result.
Stop without running later pipes
Section titled “Stop without running later pipes”A pipe short-circuits the pipeline by returning a result without calling $next:
In this example, later pipes and the final destination do not run. Use short-circuiting only when the returned type is valid for the entire pipeline; otherwise callers receive an unexpected result type.
Choose a pipe form
Section titled “Choose a pipe form”The provider’s through() call accepts several pipe forms:
| Pipe | Behavior |
|---|---|
| Class name | Resolved through the container; handle() is called when present, otherwise the object must be invokable |
| Object | Used directly; handle() is called when present, otherwise the object must be invokable |
| Callable | Called directly with the current value and $next |
ClassName:param1,param2 |
Resolved through the container and given the extra string parameters after $next |
Prefer class-name pipes for reusable application behavior because their constructor dependencies remain injectable. Closures are useful for a small operation local to one configured pipeline:
The consumer uses thenReturn() when the fully processed value is the result. It uses then() when the destination performs the final operation, such as saving the normalized product.
Pass literal parameters to a pipe
Section titled “Pass literal parameters to a pipe”Append comma-separated string parameters after the class name in the provider’s pipe list:
The matching pipe receives them after the value and $next:
Parameters from the pipe string are always strings. Prefer constructor injection and provider configuration for service dependencies or structured configuration.
Use a different pipe method
Section titled “Use a different pipe method”Pipes use handle() by default. Configure via() when every object pipe in that pipeline exposes another method:
Both classes in this example must expose process( $value, Closure $next ). Use through() to replace the configured pipe list and pipe() to append additional pipes.
Testing
Section titled “Testing”Test each pipe in isolation
Section titled “Test each pipe in isolation”Call the pipe with an identity closure so the test observes the value passed to the next stage:
Test the configured order once
Section titled “Test the configured order once”Use the real container for one focused test that resolves the consumer and proves the provider-configured pipeline runs in the intended order:
Keep most tests on individual pipes. The pipeline package already owns the generic chaining behavior; application tests need to prove only their transformations, short circuits, and configured order.