Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 821
Mayank Kumar Avatar asked Oct 16 '25 14:10

Mayank Kumar


2 Answers

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

            });
        };);
    };);
};
like image 166
Rwd Avatar answered Oct 18 '25 07:10

Rwd


Next is a Closure, a variable of anonymous function. In your code, you have return $next($request);. $next is a Closure, based on your second method parameter. That means the return value of your method is something that the anonymous function return.

For example:

// this is the Closure.
$next = function ($parameter) {
    return $parameter . ' This message is modified by $next';
};

public function handle($message, Closure $next)
{
    return $next($message);
}

// test the output
$message = 'Hello World!';
echo handle($message, $next); // output will be 'Hello World! This message is modified by $next'
like image 23
Rendy Eko Prastiyo Avatar answered Oct 18 '25 05:10

Rendy Eko Prastiyo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!