Here is a handle() method from Laravel's ValidatePostSize:
public function handle($request, Closure $next)
{
$max = $this->getPostMaxSize();
if ($max > 0 && $request->server('CONTENT_LENGTH') > $max) {
throw new PostTooLargeException;
}
return $next($request);
}
Now, this method is called using $next($request) for another Middleware. What my understanding is that handle() method get translated to $next. I want to know how this happens under the hood.
To pass the request deeper into the application (allowing the middleware to "pass"), simply call the $next callback with the $request. https://laravel.com/docs/5.4/middleware#defining-middleware
When Laravel is handling a request it runs all the applicable middleware in the stack. Middleware can be set to run before and/or after the route/controller method.
To be able to do this Laravel uses Illuminate\Pipeline\Pipeline
. Essentially, it uses array_reduce
to iterate over the middleware stack which then returns a Closure
to execute that middleware. The beauty to this comes from using array_reverse
allowing the next middleware execution to be passed to the previous one.
To elaborate a little bit more:
When the Illuminate\Foundation\Http\Kernel@handle
is called it builds up the response with sendRequestThroughRouter
which has the following in it:
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
Pipeline
is Illuminate\Routing\Pipeline
which extends Illuminate\Pipeline\Pipeline
.
The then()
method above is essentially:
->then(function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
})
Then means that we're starting off with a closure that accepts the end results (remember at this point that closure won't have been called).
Then, in the then()
method, the array_reduce
and array_reverse
section as mentioned above happens.
Here is a simplified example of what actually happens in the then()
method (this assumes you know how array_reduce
works):
function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->middlewares),
//Remember $nextClosure is going to be the closure returned
//from the previous iteration
function ($nextClosure, $middlewareClass) {
//This is the $next closure you see in your middleware
return function ($request) use ($nextClosure, $middlewareClass) {
//Resolve the middleware
$middleware = app($middlewareClass);
//Call the middleware
return $middleware->{$this->method}($request, $nextClosure);
};
},
//The finial closure that will be called that resolves the destination
function ($request) use ($destination) {
return $destination($request);
}
);
return $pipeline($this->request);
}
Say we have 3 middlewares:
[
One::class,
Two::class,
Three::class,
];
The $pipeline
variable above would basically be:
function ($request) {
return app(One::class)->handle($request, function ($request) {
return app(Two::class)->handle($request, function ($request) {
return app(Three::class)->handle($request, function ($request) {
return $destination($request);
});
};);
};);
};