0

The Pipeline class — Illuminate/Pipeline/Pipeline.php internals

Expert5 min read·lv-07-006
laravel-srcframework

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():

php
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 pipes

How 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
<?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