Streamlining Data Processing with Laravel's Pipeline Pattern
The Pipeline pattern in Laravel provides an elegant way to pass data through a series of operations. This pattern is particularly useful for complex workflows or when you need to apply a sequence of transformations to your data. Let's explore how to implement and leverage the Pipeline pattern in your Laravel applications.
Basic Pipeline Usage
Here's a basic example of using Laravel's pipeline:
use Illuminate\Support\Facades\Pipeline;
$result = Pipeline::send($order)
->through([
ValidateOrder::class,
ApplyDiscount::class,
CalculateTax::class,
ProcessPayment::class,
])
->then(fn ($order) => $order->complete());
In this example, an $order
object is passed through a series of operations before being completed.
Creating Pipeline Steps
Each step in the pipeline should be a class with an __invoke
method:
class ValidateOrder
{
public function __invoke($order, $next)
{
// Perform validation logic
if (!$order->isValid()) {
throw new InvalidOrderException();
}
return $next($order);
}
}
Passing Additional Parameters
You can pass additional parameters to your pipeline steps:
$result = Pipeline::send($order)
->through([
ValidateOrder::class,
[ApplyDiscount::class, $discountCode],
[CalculateTax::class, $taxRate],
ProcessPayment::class,
])
->then(fn ($order) => $order->complete());
In your pipeline step:
class ApplyDiscount
{
public function __invoke($order, $next, $discountCode)
{
// Apply discount logic using $discountCode
return $next($order);
}
}
Using the Pipeline in Controllers
Implement the pipeline in your controller for clean, modular code:
class OrderController extends Controller
{
public function store(Request $request)
{
$order = new Order($request->all());
return Pipeline::send($order)
->through([
ValidateOrder::class,
SaveOrder::class,
NotifyCustomer::class,
])
->then(fn ($order) => response()->json($order, 201));
}
}
Error Handling in Pipelines
Handle exceptions within your pipeline steps:
class ProcessPayment
{
public function __invoke($order, $next)
{
try {
// Process payment logic
PaymentGateway::charge($order->total);
} catch (PaymentFailedException $e) {
return response()->json(['error' => 'Payment failed'], 400);
}
return $next($order);
}
}
Creating Reusable Pipelines
For frequently used pipelines, create a dedicated class:
class OrderProcessingPipeline
{
public function __invoke($order)
{
return Pipeline::send($order)
->through([
ValidateOrder::class,
ApplyDiscount::class,
CalculateTax::class,
ProcessPayment::class,
])
->then(fn ($order) => $order->complete());
}
}
Use it in your controller:
public function store(Request $request, OrderProcessingPipeline $pipeline)
{
$order = new Order($request->all());
return $pipeline($order);
}
Conditional Pipeline Steps
Add or remove steps based on conditions:
$steps = [ValidateOrder::class, SaveOrder::class];
if ($request->has('discount_code')) {
$steps[] = ApplyDiscount::class;
}
$steps[] = ProcessPayment::class;
return Pipeline::send($order)
->through($steps)
->then(fn ($order) => response()->json($order, 201));
The Pipeline pattern in Laravel offers a clean, modular approach to handling complex workflows. By breaking down your logic into small, focused steps, you can create more maintainable and testable code. This pattern is particularly useful for processes like order processing, user registration, or any scenario where you need to apply a series of operations to your data in a specific order.
If this guide was helpful to you, subscribe to my daily newsletter and give me a follow on X/Twitter. It helps a lot!