The Pipeline class — Illuminate/Pipeline/Pipeline.php internals
Concept
Illuminate\Pipeline\Pipeline is the class that powers Laravel's middleware system. Understanding it lets you use the same pattern in your own code for processing data through a series of stages.
The Pipeline pattern: A pipeline takes a "passable" (the thing being processed), runs it through a series of "pipes" (processors), and returns a final result. Each pipe can transform the passable and pass it to the next, or stop the chain and return immediately.
Pipeline::send()->through()->then():
app(\Illuminate\Pipeline\Pipeline::class)
->send($passable) // the thing to process
->through($pipes) // array of pipe classes or closures
->then($destination); // final callback after all pipesHow middleware uses Pipeline: App\Http\Kernel builds a Pipeline with the request as the passable, the middleware stack as the pipes, and the controller dispatch as the destination. Each middleware's handle($request, $next) corresponds to a pipe receiving the passable and $next being the next stage.
Custom pipelines in application code: Use Pipeline to implement multi-stage data processing: import pipelines, payment processing steps, content transformation chains.
Pipe classes vs closures: Pipes can be class names (resolved from the container, must implement handle($passable, $next)), objects (same interface), or closures.
Code Example
<?php
// Using Pipeline for a custom processing chain
use Illuminate\Pipeline\Pipeline;
// Data transformation pipeline
$order = app(Pipeline::class)
->send($rawOrderData)
->through([
\App\Pipeline\ValidateOrderData::class,
\App\Pipeline\ApplyDiscounts::class,
\App\Pipeline\CalculateTax::class,
\App\Pipeline\CheckInventory::class,
])
->then(fn($orderData) => Order::create($orderData));
// Pipe class interface — same as middleware
class ApplyDiscounts
{
public function handle(array $orderData, \Closure $next): array
{
if ($orderData['user_type'] === 'vip') {
$orderData['discount'] = 0.1; // 10% discount
}
return $next($orderData); // pass to next pipe
}
}
// Short-circuit from a pipe
class CheckInventory
{
public function handle(array $orderData, \Closure $next): mixed
{
foreach ($orderData['items'] as $item) {
if (!$this->inventory->hasStock($item['product_id'], $item['quantity'])) {
// Short-circuit — don't call $next
throw new \App\Exceptions\InsufficientInventoryException($item['product_id']);
}
}
return $next($orderData);
}
}
// Using closures in the pipeline
$result = app(Pipeline::class)
->send($initialValue)
->through([
fn($val, $next) => $next($val * 2), // double
fn($val, $next) => $next($val + 10), // add 10
fn($val, $next) => $next(max($val, 0)), // clamp to 0
])
->thenReturn(); // returns the final passable (shorthand for ->then(fn($v) => $v))
// The kernel's pipeline (simplified):
$response = (new Pipeline($container))
->send($request)
->through($this->middleware) // all global + group + route middleware
->then($this->dispatchToRouter()); // runs the controller