Pipeline (Laravel) — request flowing through ordered middleware handlers
Concept
Pipeline (Laravel) — the mechanism that passes an object (a "traveller") through a series of stages ("pipes"), where each stage can transform the object and optionally pass it to the next stage.
The term: Laravel's Pipeline class implements the pipeline design pattern. In the context of HTTP, middleware IS a pipeline — every incoming request flows through a series of middleware handlers before reaching the controller.
Laravel HTTP Pipeline: When a request arrives at index.php → Kernel::handle(), the kernel passes the request through an array of middleware. Each middleware is a "pipe." Each pipe calls $next($request) to continue, or returns early to short-circuit.
The underlying implementation: array_reduce() with the pipes reversed. The pipeline wraps each pipe around the next, building nested closures. The innermost closure is the destination (the controller). Calling the outermost closure calls them all in order.
Pipeline API:
app(Pipeline::class)— get an instance.->send($traveller)— the object being passed through.->through([$pipes])— the ordered array of pipe classes.->thenReturn()— execute and return the result.->then($destination)— execute with a final callback.
Pipes can be:
- Class names:
CheckMaintenanceMode::class— must have ahandle($passable, $next)method. - Closures:
function($passable, $next) { return $next($passable); }.
Real uses in Laravel:
- HTTP middleware pipeline (the request lifecycle).
- Eloquent global scopes (applied in sequence).
Bus::dispatch()wraps job dispatching through middleware.
Code Example
<?php
use Illuminate\Pipeline\Pipeline;
// BASIC PIPELINE USAGE
$result = app(Pipeline::class)
->send(['name' => ' Alice ', 'email' => 'ALICE@EXAMPLE.COM', 'age' => '25'])
->through([
TrimStrings::class,
LowercaseEmail::class,
CastTypes::class,
])
->thenReturn();
// Input: ['name' => ' Alice ', 'email' => 'ALICE@EXAMPLE.COM', 'age' => '25']
// Output: ['name' => 'Alice', 'email' => 'alice@example.com', 'age' => 25]
// PIPE classes — each must have handle($passable, $next)
class TrimStrings
{
public function handle(array $data, \Closure $next): mixed
{
$data = array_map(fn($v) => is_string($v) ? trim($v) : $v, $data);
return $next($data); // MUST call $next to continue
}
}
class LowercaseEmail
{
public function handle(array $data, \Closure $next): mixed
{
if (isset($data['email'])) $data['email'] = strtolower($data['email']);
return $next($data);
}
}
class CastTypes
{
public function handle(array $data, \Closure $next): mixed
{
if (isset($data['age'])) $data['age'] = (int) $data['age'];
return $next($data);
}
}
// SHORT-CIRCUIT — a pipe can stop the pipeline and return early
class RequireApiKey
{
public function handle(Request $request, \Closure $next): mixed
{
if (!$request->header('X-API-Key')) {
return response()->json(['error' => 'API key required'], 401);
// $next() is NOT called — pipeline stops here!
}
return $next($request);
}
}
// THE ACTUAL HTTP MIDDLEWARE PIPELINE in Laravel's Kernel
// protected $middleware = [
// \App\Http\Middleware\TrustProxies::class,
// \Illuminate\Http\Middleware\HandleCors::class,
// \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
// \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
// \App\Http\Middleware\TrimStrings::class,
// ];
// Each request flows through ALL of these before hitting your controller
// PIPELINE INTERNALS (simplified implementation)
function runPipeline($traveller, array $pipes, callable $destination): mixed
{
// Build the pipeline from right to left (so execution is left to right)
$pipeline = array_reduce(
array_reverse($pipes),
function ($next, $pipe) {
return function ($passable) use ($next, $pipe) {
return (new $pipe)->handle($passable, $next);
};
},
$destination // innermost: the controller/route handler
);
return $pipeline($traveller);
}