Pipelines
Chain handlers together with data transformation
Pipelines allow you to chain multiple handlers together, passing data through each stage with optional transformations.
Basic Pipeline
use Esegments\LaravelExtensions\Pipeline\ExtensionPipeline;
$result = ExtensionPipeline::make()
->through([
ValidateOrderHandler::class,
CalculateTaxHandler::class,
ApplyDiscountsHandler::class,
FinalizeOrderHandler::class,
])
->send($order)
->thenReturn();
Pipeline with Extensions
Combine pipelines with extension points:
use Esegments\LaravelExtensions\Facades\Extensions;
// Register pipeline stages via extensions
Extensions::register('order.process', ValidateOrderHandler::class, 100);
Extensions::register('order.process', CalculateTaxHandler::class, 90);
Extensions::register('order.process', ApplyDiscountsHandler::class, 80);
// Execute as pipeline
$result = Extensions::dispatch('order.process', $order)
->asPipeline()
->thenReturn();
Transformation Between Stages
Transform data between pipeline stages:
ExtensionPipeline::make()
->through([
function ($data, $next) {
// Pre-process
$data['started_at'] = now();
// Pass to next stage
$result = $next($data);
// Post-process
$result['completed_at'] = now();
return $result;
},
ProcessDataHandler::class,
])
->send($data)
->thenReturn();
Conditional Stages
Add stages conditionally:
$pipeline = ExtensionPipeline::make()
->through([
ValidateHandler::class,
]);
if ($needsApproval) {
$pipeline->pipe(ApprovalHandler::class);
}
$pipeline->pipe(FinalizeHandler::class);
$result = $pipeline->send($data)->thenReturn();
Pipeline with Fallback
Handle failures gracefully:
$result = ExtensionPipeline::make()
->through($handlers)
->send($data)
->onFailure(function ($exception, $data) {
Log::error('Pipeline failed', [
'error' => $exception->getMessage(),
'data' => $data,
]);
return $this->getDefaultResult($data);
})
->thenReturn();
Creating Pipeline Handlers
Pipeline handlers receive the data and a closure to call the next stage:
class CalculateTaxHandler
{
public function handle($order, Closure $next)
{
// Calculate tax
$order->tax = $order->subtotal * 0.1;
// Pass to next handler
return $next($order);
}
}
Invokable Handlers
class CalculateTaxHandler
{
public function __invoke($order, Closure $next)
{
$order->tax = $this->taxService->calculate($order);
return $next($order);
}
}
Aborting a Pipeline
Stop pipeline execution early:
class ValidateOrderHandler
{
public function handle($order, Closure $next)
{
if (!$order->isValid()) {
// Don't call $next - pipeline stops here
return [
'success' => false,
'error' => 'Order validation failed',
];
}
return $next($order);
}
}
Pipeline Events
Listen to pipeline lifecycle events:
ExtensionPipeline::make()
->through($handlers)
->beforeEach(function ($handler, $data) {
Log::debug("Starting handler: " . get_class($handler));
})
->afterEach(function ($handler, $data, $result) {
Log::debug("Completed handler: " . get_class($handler));
})
->send($data)
->thenReturn();
Nested Pipelines
Compose pipelines within pipelines:
class OrderProcessingHandler
{
public function handle($order, Closure $next)
{
// Run sub-pipeline for order items
foreach ($order->items as $item) {
ExtensionPipeline::make()
->through([
ValidateItemHandler::class,
CalculateItemPriceHandler::class,
])
->send($item)
->thenReturn();
}
return $next($order);
}
}
Best Practices
1. Keep Handlers Focused
Each handler should do one thing:
// Good - single responsibility
class CalculateTaxHandler { }
class ApplyDiscountHandler { }
class UpdateInventoryHandler { }
// Bad - too many responsibilities
class ProcessEverythingHandler { }
2. Use Dependency Injection
class NotifyCustomerHandler
{
public function __construct(
private NotificationService $notifications
) {}
public function handle($order, Closure $next)
{
$this->notifications->send($order->customer, new OrderConfirmation($order));
return $next($order);
}
}
3. Handle Errors Appropriately
class ExternalApiHandler
{
public function handle($data, Closure $next)
{
try {
$response = $this->api->call($data);
$data['api_response'] = $response;
} catch (ApiException $e) {
// Decide: abort or continue with default
$data['api_response'] = $this->getDefaultResponse();
}
return $next($data);
}
}