phplaravellaravel-middleware

How handle() method of Laravel middleware is called using 'Clousre $next' in another Middleware?


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.


Solution

  • 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);
    
                });
            };);
        };);
    };