search
HomeBackend DevelopmentPHP TutorialCore interpretation of Laravel middleware (Middleware)

Core interpretation of Laravel middleware (Middleware)

Feb 07, 2018 pm 02:58 PM
laravelmiddlewaremiddleware


This article mainly shares with you the core interpretation of Laravel middleware (Middleware). Middleware (Middleware) in Laravel plays the role of filtering the HTTP request object (Request) entering the application and improving the exit. The role of the application's HTTP response object (Reponse), and the application of multiple middlewares can filter requests layer by layer and gradually improve the response. This achieves decoupling of the program. If there is no middleware, we must complete these steps in the controller, which will undoubtedly cause the controller to be bloated.

To give a simple example, on an e-commerce platform, a user can be either an ordinary user shopping on the platform or a seller user after opening a store. The user system of these two types of users is often the same. set, then we only need to apply two middlewares to complete the identity authentication of the seller user in the controller that only the seller user can access:

     

class MerchantController extends Controller{
    public function __construct()
    {
        $this->middleware('auth');
        $this->middleware('mechatnt_auth');
    }
}

General user authentication is done in the auth middleware. After success, the HTTP Request will go to the merchant_auth middleware to authenticate the merchant user information. After both middleware pass, the HTTP Request will be You can enter the controller method you want to go to. Using middleware, we can extract these authentication codes into corresponding middleware, and we can freely combine multiple middleware according to needs to filter HTTP Requests.

Another example is the VerifyCsrfToken middleware that Laravel automatically applies to all routes. When HTTP Requst enters the application and passes through the VerifyCsrfToken middleware, the Token will be verified to prevent cross-site request forgery. , appropriate cookies will be added to the response before the Http Response leaves the application. (Starting from laravel 5.5, CSRF middleware is only automatically applied to web routing)

In the above example, the one that filters the request is called the pre-middleware, and the one that completes the response is called the post-middleware. A picture can be used to mark the entire process:
Core interpretation of Laravel middleware (Middleware)

The above outlines the role of 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, please refer to Official Documentation.

Let’s take a look at how middleware is implemented in Laravel. The design of middleware applies a design pattern called decorator.

After Laravel instantiates Application, it will parse the Http Kernel object from the service container. It can also be seen from the name of the class that Http Kernel is the core responsible for HTTP requests and responses in Laravel.

/**
 * @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);

You can see it in index.php, from the service container Parse out the Http Kernel because the implementation class of the Illuminate\Contracts\Http\Kernel interface is bound in bootstrap/app.phpApp\Http\Kernel So $kernel is actually an object of class App\Http\Kernel.
After parsing out the Http Kernel, Laravel passes the request object entering the application to the handle method of the Http Kernel. The handle method is responsible for processing the request object flowing 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 application occurs in $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 initializes the Application , this part is explained in detail in the previous article explaining service providers. Laravel transmits the request object through the Pipeline object. In the Pipeline, the request object reaches an action of the controller through the pre-operation of the middleware defined in the Http Kernel or direct closure processing to obtain 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 就是要通过的中间件
    $pipes = array_reverse($this->pipes);    //$this->passable就是Request对象    return call_user_func(
        array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable
    );
}protected function getInitialSlice(Closure $destination){    return function ($passable) use ($destination) {        return call_user_func($destination, $passable);
    };
}//Http Kernel的dispatchToRouter是Piple管道的终点或者叫目的地protected function dispatchToRouter(){    return function ($request) {        $this->app->instance('request', $request);        return $this->router->dispatch($request);
    };
}

The above function looks confusing. Let’s first look at the explanation of its callback function parameters in array_reduce:

mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )
array_reduce() 将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。
callback ( mixed $carry , mixed $item )carry携带上次迭代里的值; 如果本次迭代是第一次,那么这个值是 initial。item 携带了本次迭代的值。

getInitialSlice method, its return value is the initial value of the $carray parameter passed to the callbakc function. This value is now a closure. I put the two methods getInitialSlice and Http Kernel's dispatchToRouter Merged, 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 let’s look at the callback of array_reduce:

           

//Pipeline protected function getSlice(){    return function ($stack, $pipe) {        return 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));
            }
        };
    };
}//Pipleline的父类BasePipeline的getSlice方法protected function getSlice(){    return function ($stack, $pipe) {        return function ($passable) use ($stack, $pipe) {            if ($pipe instanceof Closure) {                return call_user_func($pipe, $passable, $stack);
            } elseif (! is_object($pipe)) {                //解析中间件名称和参数 ('throttle:60,1')                list($name, $parameters) = $this->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);
        };
    };
}

注:在Laravel5.5版本里 getSlice这个方法的名称换成了carray, 两者在逻辑上没有区别,所以依然可以参照着5.5版本里中间件的代码来看本文。

getSlice会返回一个闭包函数, $stack在第一次调用getSlice时它的值是$firstSlice, 之后的调用中就它的值就是这里返回的值个闭包了:

           

$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返回的闭包里又会去调用父类的getSlice方法,他返回的也是一个闭包,在闭包会里解析出中间件对象、中间件参数(无则为空数组), 然后把$passable(请求对象), $stack和中间件参数作为中间件handle方法的参数进行调用。

上面封装的有点复杂,我们简化一下,其实getSlice的返回值就是:

           

$stack = function ($passable) use ($stack, $pipe) {                //解析中间件和中间件参数,中间件参数用$parameter代表,无参数时为空数组
               $parameters = array_merge([$passable, $stack], $parameters)               return $pipe->handle($parameters)
};

array_reduce每次调用callback返回的闭包都会作为参数$stack传递给下一次对callback的调用,array_reduce执行完成后就会返回一个嵌套了多层闭包的闭包,每层闭包用到的外部变量$stack都是上一次之前执行reduce返回的闭包,相当于把中间件通过闭包层层包裹包成了一个洋葱。

在then方法里,等到array_reduce执行完返回最终结果后就会对这个洋葱闭包进行调用:

           

return call_user_func( array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable);

这样就能依次执行中间件handle方法,在handle方法里又会去再次调用之前说的reduce包装的洋葱闭包剩余的部分,这样一层层的把洋葱剥开直到最后。通过这种方式让请求对象依次流过了要通过的中间件,达到目的地Http Kernel 的dispatchToRouter方法。

通过剥洋葱的过程我们就能知道为什么在array_reduce之前要先对middleware数组进行反转, 因为包装是一个反向的过程, 数组$pipes中的第一个中间件会作为第一次reduce执行的结果被包装在洋葱闭包的最内层,所以只有反转后才能保证初始定义的中间件数组中第一个中间件的handle方法会被最先调用。

上面说了Pipeline传送请求对象的目的地是Http Kernel 的dispatchToRouter方法,其实到远没有到达最终的目的地,现在请求对象了只是刚通过了\App\Http\Kernel类里$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,
];

当请求对象进入dispatchToRouter方法后,请求对象在被Router dispatch派发给路由时会进行收集路由上应用的中间件和控制器里应用的中间件。

           

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->bound('middleware.disable') &&                            $this->container->make('middleware.disable') === true;    //收集路由和控制器里应用的中间件
    $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);    return (new Pipeline($this->container))
                    ->send($request)
                    ->through($middleware)
                    ->then(function ($request) use ($route) {                        return $this->prepareResponse(
                            $request, $route->run()
                        );
                    });
}

收集完路由和控制器里应用的中间件后,依赖时利用Pipeline对象来传送请求对象通过收集上来的这些中间件然后到达最终的目的地,在这里会执行路由对应的控制器方法生成响应对象,然后响应对象会依次来通过上面应用的所有中间件的后置操作,最终离开应用被发送给客户端。

限于篇幅和为了文章的可读性,收集路由和控制器中间件然后执行路由对应的处理方法的过程我就不在这里详述了,感兴趣的同学可以自己去看Router的源码,本文的目的还是主要为了梳理laravel是如何设计中间件的以及如何执行它们的,希望能对感兴趣的朋友有帮助。

相关推荐:

Laravel中间件实现原理详解

laravel中间件中的Closure $next是什么意思

关于laravel中间件

The above is the detailed content of Core interpretation of Laravel middleware (Middleware). For more information, please follow other related articles on the PHP Chinese website!

Statement
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
What is the difference between unset() and session_destroy()?What is the difference between unset() and session_destroy()?May 04, 2025 am 12:19 AM

Thedifferencebetweenunset()andsession_destroy()isthatunset()clearsspecificsessionvariableswhilekeepingthesessionactive,whereassession_destroy()terminatestheentiresession.1)Useunset()toremovespecificsessionvariableswithoutaffectingthesession'soveralls

What is sticky sessions (session affinity) in the context of load balancing?What is sticky sessions (session affinity) in the context of load balancing?May 04, 2025 am 12:16 AM

Stickysessionsensureuserrequestsareroutedtothesameserverforsessiondataconsistency.1)SessionIdentificationassignsuserstoserversusingcookiesorURLmodifications.2)ConsistentRoutingdirectssubsequentrequeststothesameserver.3)LoadBalancingdistributesnewuser

What are the different session save handlers available in PHP?What are the different session save handlers available in PHP?May 04, 2025 am 12:14 AM

PHPoffersvarioussessionsavehandlers:1)Files:Default,simplebutmaybottleneckonhigh-trafficsites.2)Memcached:High-performance,idealforspeed-criticalapplications.3)Redis:SimilartoMemcached,withaddedpersistence.4)Databases:Offerscontrol,usefulforintegrati

What is a session in PHP, and why are they used?What is a session in PHP, and why are they used?May 04, 2025 am 12:12 AM

Session in PHP is a mechanism for saving user data on the server side to maintain state between multiple requests. Specifically, 1) the session is started by the session_start() function, and data is stored and read through the $_SESSION super global array; 2) the session data is stored in the server's temporary files by default, but can be optimized through database or memory storage; 3) the session can be used to realize user login status tracking and shopping cart management functions; 4) Pay attention to the secure transmission and performance optimization of the session to ensure the security and efficiency of the application.

Explain the lifecycle of a PHP session.Explain the lifecycle of a PHP session.May 04, 2025 am 12:04 AM

PHPsessionsstartwithsession_start(),whichgeneratesauniqueIDandcreatesaserverfile;theypersistacrossrequestsandcanbemanuallyendedwithsession_destroy().1)Sessionsbeginwhensession_start()iscalled,creatingauniqueIDandserverfile.2)Theycontinueasdataisloade

What is the difference between absolute and idle session timeouts?What is the difference between absolute and idle session timeouts?May 03, 2025 am 12:21 AM

Absolute session timeout starts at the time of session creation, while an idle session timeout starts at the time of user's no operation. Absolute session timeout is suitable for scenarios where strict control of the session life cycle is required, such as financial applications; idle session timeout is suitable for applications that want users to keep their session active for a long time, such as social media.

What steps would you take if sessions aren't working on your server?What steps would you take if sessions aren't working on your server?May 03, 2025 am 12:19 AM

The server session failure can be solved through the following steps: 1. Check the server configuration to ensure that the session is set correctly. 2. Verify client cookies, confirm that the browser supports it and send it correctly. 3. Check session storage services, such as Redis, to ensure that they are running normally. 4. Review the application code to ensure the correct session logic. Through these steps, conversation problems can be effectively diagnosed and repaired and user experience can be improved.

What is the significance of the session_start() function?What is the significance of the session_start() function?May 03, 2025 am 12:18 AM

session_start()iscrucialinPHPformanagingusersessions.1)Itinitiatesanewsessionifnoneexists,2)resumesanexistingsession,and3)setsasessioncookieforcontinuityacrossrequests,enablingapplicationslikeuserauthenticationandpersonalizedcontent.

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

This project is in the process of being migrated to osdn.net/projects/mingw, you can continue to follow us there. MinGW: A native Windows port of the GNU Compiler Collection (GCC), freely distributable import libraries and header files for building native Windows applications; includes extensions to the MSVC runtime to support C99 functionality. All MinGW software can run on 64-bit Windows platforms.

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

Integrate Eclipse with SAP NetWeaver application server.

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser is a secure browser environment for taking online exams securely. This software turns any computer into a secure workstation. It controls access to any utility and prevents students from using unauthorized resources.

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment