The pipeline pattern passes a subject (the passable) through a series of pipes (processing steps) in sequence. Each pipe receives the output of the previous step, applies its transformation, and passes the result along.Laravel implements this pattern in Illuminate\Pipeline\Pipeline. Middleware processing is the most visible example — every HTTP request travels through a pipeline before reaching your controller.
namespace App\Pipes\Order;use App\Models\Order;use Closure;class ValidateInventoryPipe{ public function handle(Order $order, Closure $next): Order { foreach ($order->items as $item) { if ($item->product->stock < $item->quantity) { throw new \RuntimeException("Out of stock: {$item->product->name}"); } } return $next($order); }}
namespace App\Pipes\Order;use App\Models\Order;use Closure;class ReserveInventoryPipe{ public function handle(Order $order, Closure $next): Order { foreach ($order->items as $item) { $item->product->decrement('stock', $item->quantity); } return $next($order); }}
namespace App\Pipes\Order;use App\Models\Order;use App\Services\PaymentService;use Closure;class ProcessPaymentPipe{ public function __construct( protected PaymentService $payment, ) {} public function handle(Order $order, Closure $next): Order { $this->payment->charge($order->total, $order->payment_token); $order->update(['status' => 'paid']); return $next($order); }}
2
Run the pipeline
use App\Models\Order;use App\Pipes\Order\ValidateInventoryPipe;use App\Pipes\Order\ReserveInventoryPipe;use App\Pipes\Order\ProcessPaymentPipe;use Illuminate\Pipeline\Pipeline;class OrderService{ public function process(Order $order): Order { return app(Pipeline::class) ->send($order) ->through([ ValidateInventoryPipe::class, ReserveInventoryPipe::class, ProcessPaymentPipe::class, ]) ->withinTransaction() ->thenReturn(); }}
The pipes are reversed before being passed to array_reduce so that the first pipe in your array is the first to execute. The closure stack is built from the inside out.
The pipeline is sometimes called an “onion” architecture. A request enters from the outermost layer (the first pipe) and travels inward. The response then unwinds back through each layer. This is why middleware can run code both before and after calling $next().