The core interpretation of Laravel middleware (middleware)

Source: Internet
Author: User


In this paper, we share the core interpretation of Laravel Middleware (middleware), in which middleware (middleware) filters the HTTP Request object (request) into the application and improves the HTTP response object ( reponse), and can be applied through multiple middleware to filter requests, and gradually improve the corresponding. This is the decoupling of the program, if there is no middleware then we have to complete these steps in the controller, which will undoubtedly cause the controller bloated.

To give a simple example, on an e-commerce platform, users can either be a normal user on the platform shopping can also be a seller after the store is a user, these two users of the user system is often a set, then only the seller users can access the controller we only need to apply two middleware to complete the identity of the seller user authentication:

Class Merchantcontroller extends controller{public    function __construct ()    {        $this->middleware (' Auth ');        $this->middleware (' Mechatnt_auth ');}    }

In the AUTH middleware to do a common user authentication, after the successful HTTP request will go to the Merchant_auth middleware for merchant user information authentication, two middleware after the HTTP request will be able to enter the controller method to go. Using middleware, we can extract the authentication code into the corresponding middleware, and can combine multiple middleware to filter the HTTP request according to the requirement.

Again, for example, Laravel automatically to all routing application VerifyCsrfToken Middleware, the HTTP requst into the application through the VerifyCsrfToken middleware will verify token to prevent cross-site request forgery, the HTTP Response before leaving the application will add the appropriate cookie to the response. (laravel5.5 start CSRF Middleware is automatically applied to web routing only)

In the above example, the request is called the front-facing middleware, the perfect response is called the post-middleware. The entire process can be marked with a single graph:

The above outlines the role of the middleware in laravel, and what type of code should be moved from the controller to the middleware, as for how to define and use your own laravel middleware, refer to the official documentation.

Below we mainly see how to implement middleware in Laravel, middleware design has applied a design pattern called adorners.

Laravel instantiates application, the HTTP kernel object is parsed from the service container, and the name of the class can be seen as HTTP kernel is the core of the Laravel responsible for HTTP requests and responses.

/** * @var \app\http\kernel $kernel */$kernel = $app->make (illuminate\contracts\http\kernel::class); $response = $ Kernel->handle (    $request = Illuminate\http\request::capture ()); $response->send (); $kernel->terminate ($request, $response);

index.phpas you can see, the HTTP Kernel is parsed from the service container because the bootstrap/app.php Illuminate\Contracts\Http\Kernel implementation class of the interface is bound App\Http\Kernel so that $kernel is actually App\Http\Kernel the object of the class.
After parsing out the HTTP kernel, Laravel passes the request object into the application to the HTTP kernel handle method, where the handle method is responsible for processing the request object into the application and returning the response object.

/** * Handle an incoming HTTP request. * * @param  \illuminate\http\request  $request * @return \illuminate\http\response */public function handle ($ Request) {    try {        $request->enablehttpmethodparameteroverride ();        $response = $this->sendrequestthroughrouter ($request);    } catch (Exception $e) {        $this->reportexception ($e);        $response = $this->renderexception ($request, $e);    } catch (Throwable $e) {        $this->reportexception ($e = new Fatalthrowableerror ($e));        $response = $this->renderexception ($request, $e);    }    $this->app[' Events ']->dispatch (        new events\requesthandled ($request, $response)    );    return $response;}

The process of middleware filtering applications is happening $this->sendRequestThroughRouter($request) :

/** * Send The given request through the Middleware/router. * * @param  \illuminate\http\request  $request * @return \illuminate\http\response */protected function Sendrequestthroughrouter ($request) {    $this->app->instance (' request ', $request);    Facade::clearresolvedinstance (' request ');    $this->bootstrap ();    Return (new Pipeline ($this->app))                ->send ($request)                ->through ($this->app-> Shouldskipmiddleware ()? []: $this->middleware)                ->then ($this->dispatchtorouter ());}

The first half of this method is the initialization of the application, which is explained in detail in the previous article explaining the service provider. Laravel the request object through the pipeline (pipeline) object, the request object in pipeline in turn, through the pre-operation of the middleware defined in HTTP kernel, arrives at the Controller's action or direct closure processing to get the response object.

Look at these methods in pipeline:

 Public function Send ($passable) {$this->passable = $passable; return $this;}    Public function through ($pipes) {$this->pipes = Is_array ($pipes)? $pipes: Func_get_args (); return $this;}        Public function then (Closure $destination) {$firstSlice = $this->getinitialslice ($destination);    Pipes is the middleware to be passed $pipes = Array_reverse ($this->pipes); $this->passable is the request object return Call_user_func (Array_reduce ($pipes, $this->getslice (), $firstSlice), $this->passable);} protected function Getinitialslice (Closure $destination) {return function ($passable) use ($destination) {retur    N Call_user_func ($destination, $passable); };}        The dispatchtorouter of the Http kernel is the end of the Piple pipeline or the destination protected function Dispatchtorouter () {return function ($request) {        $this->app->instance (' request ', $request);    return $this->router->dispatch ($request); };}

The above function looks faint, so let's take a look at the explanation of its callback function parameters in Array_reduce:

Mixed Array_reduce (array $array, callable $callback [, mixed $initial = NULL]) array_reduce () The callback function callback iteratively to AR Each cell in the ray array, simplifying the array to a single value. Callback (mixed $carry, mixed $item) carry carry the value in the last iteration, and if this iteration is the first time, then this value is initial. Item carries the value of this iteration.

The Getinitialslice method, whose return value is the initial value of the $carray parameter passed to the CALLBAKC function, is now a closure, and I put Getinitialslice and HTTP Kernel Dispatchtorouter These two methods merge, now the value of $firstslice is:

$destination = function ($request) {    $this->app->instance (' request ', $request);    return $this->router->dispatch ($request);}; $firstSlice = function ($passable) use ($destination) {    return Call_user_func ($destination, $passable);};

Next we look at Array_reduce's callback:

Pipeline protected function Getslice () {return function ($stack, $pipe) {return function ($passable) use ($st                ACK, $pipe) {try {$slice = Parent::getslice ();            Return Call_user_func ($slice ($stack, $pipe), $passable);            } catch (Exception $e) {return $this->handleexception ($passable, $e);            } catch (Throwable $e) {return $this->handleexception ($passable, New Fatalthrowableerror ($e));    }        }; };} The Getslice method of the Pipleline parent class Basepipeline protected function Getslice () {return function ($stack, $pipe) {return fun Ction ($passable) use ($stack, $pipe) {if ($pipe instanceof Closure) {return Call_user_func ($p            Ipe, $passable, $stack); } elseif (! Is_object ($pipe)) {//parse middleware name and parameters (' throttle:60,1 ') list ($name, $parameters) = $t                His->parsepipestring ($pipe); $pipe = $this->container->make ($name);            $parameters = Array_merge ([$passable, $stack], $parameters);            } else{$parameters = [$passable, $stack];        }//$this->method = handle return Call_user_func_array ([$pipe, $this->method], $parameters);    }; };}

Note: In the Laravel5.5 version getslice the name of this method is replaced by CArray, there is no logical difference between the two, so you can still refer to the 5.5 version of the middleware code to see this article.

Getslice returns a closure function $stack the first call to Getslice when its value is $firstslice, after which the value is the value returned here is a closure:

$stack = function ($passable) use ($stack, $pipe) {            try {                $slice = Parent::getslice ();                Return Call_user_func ($slice ($stack, $pipe), $passable);            } catch (Exception $e) {                return $this->handleexception ($passable, $e);            } catch (Throwable $e) {                return $ This->handleexception ($passable, New Fatalthrowableerror ($e));            } };

Getslice returned in the closure will go to call the parent class Getslice method, he returned a closure, in the closure of the middleware object, middleware parameters (empty array), and then put the $passable (Request object), $ Stack and middleware parameters are called as parameters of the middleware handle method.

The above package is a bit more complex, we simplify, in fact, the return value of Getslice is:

$stack = function ($passable) use ($stack, $pipe) {                //parse middleware and middleware parameters, middleware parameters are represented by $parameter, empty arrays when no arguments               $parameters = Array_merge ([$passable, $stack], $parameters)               return $pipe->handle ($parameters)};

Array_reduce each call to callback returns a closure that is passed as a parameter $stack to the next call to callback, and when the Array_reduce execution is completed, a closure with nested multilayer closures is returned, and the external variable $ for each packet closure The stack is a closure that was previously executed by the reduce return, which is equivalent to wrapping the middleware into an onion through the closure layer.

In the then method, the onion closure is called when the Array_reduce executes and returns the final result:

Return Call_user_func (Array_reduce ($pipes, $this->getslice (), $firstSlice), $this->passable);

In order to execute the middleware handle method in turn, in the handle method will again go to call the previously said reduce packaging onion closure of the remaining parts, so that layer of onion peel off until the end. In this way, the request object flows through the middleware in sequence to reach the destination HTTP Kernel dispatchToRouter method.

By stripping the onion, we can see why the middleware array is reversed before array_reduce because the wrapper is a reverse process, and the first middleware in the array $pipes is wrapped in the inner layer of the onion closure as the result of the first reduce execution , the handle method of the first middleware in the initial defined middleware array is only guaranteed to be called first.

It says that the destination of the pipeline transfer Request object is the HTTP Kernel dispatchToRouter method, in fact, far from reaching the final destination, now the request object is just passing through the \App\Http\Kernel class in $middleware the properties listed in the several middleware:

protected $middleware = [    \illuminate\foundation\http\middleware\checkformaintenancemode::class,    \ Illuminate\foundation\http\middleware\validatepostsize::class,    \app\http\middleware\trimstrings::class,    \illuminate\foundation\http\middleware\convertemptystringstonull::class,    \app\http\middleware\ Trustproxies::class,];

When the request object enters the dispatchToRouter method, the request object is sent to the route by the router dispatch to collect the middleware applied on the routing and middleware used in the controller.

Public Function Dispatch (Request $request) {$this->currentrequest = $request;    $response = $this->dispatchtoroute ($request); return $this->prepareresponse ($request, $response);} Public Function Dispatchtoroute (Request $request) {return $this->runroute ($request, $this->findroute ($request) );} protected function Runroutewithinstack (Route $route, Request $request) {$shouldSkipMiddleware = $this->CONTAINER-&G  T;bound (' middleware.disable ') && $this->container->make (' middleware.disable ') = = =    True Collects middleware $middleware for routing and controller applications = $shouldSkipMiddleware?    []: $this->gatherroutemiddleware ($route); Return (new Pipeline ($this->container))->send ($request)->through ($middlew IS)->then (function ($request) use ($route) {return $this->preparerespon                      Se ($request, $route->run ()  ); });}

After collecting the middleware applied in the routing and controller, relying on the pipeline object to transfer the request object through the collected middleware and then reach the final destination, where the corresponding controller method is executed to generate the response object, The response object is then sequentially routed through the post-operation of all middleware applied above, leaving the app to be sent to the client.

Limited to space and for the readability of the article, collect Routing and controller middleware and then execute the route corresponding to the processing method of the process I am not here in detail, interested students can go to see the source of router, The purpose of this paper is mainly to comb how laravel is designed and how to implement them, hoping to be helpful to interested friends.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.