laravel5.5源碼筆記(6、中間件)

laravel中的中間件做爲一個請求與響應的過濾器,主要分爲兩個功能。php

一、在請求到達控制器層以前進行攔截與過濾,只有經過驗證的請求才能到達controller層laravel

二、或者是在controller中運算完的數據或頁面響應返回前進行過濾,經過驗證的響應才能返回給客戶端web

中間件通常經過artisan命令建立bootstrap

php artisan make:middleware 中間件名稱

命令行建立的中間件會保存在/app/Http/Middleware文件夾裏數組

這些中間件須要手動添加在/app/Http/Kernel.php文件的配置中,這個文件繼承了kernel核心,在應用入口初始化kernel核心時,vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php這個kernel文件中的配置便覆蓋了kernel核心中的相關配置,其中固然便包括了middleware中間件。咱們能夠在kernel類的構造方法第一行添加打印語句,分別打印$this->middleware,$this->middlewareGroups而後咱們就會看見以前寫在/app/Http/Kernel.php文件中的中間件了。瀏覽器

不過不止這裏一處,在路由加載的時候,也會加載一次系統默認的web數組中的中間件,在返回響應對象的時候進行過濾,這個稍後再看。閉包

接下來看一下中間件是在哪裏開始過濾的,在第一篇關於入口文件的博文中,咱們瞭解到laravel是從kernel核心中的handle方法開始的。app

    public function handle($request)
    {
        try {
            //啓用http方法覆蓋參數
            $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;
    }

    protected function sendRequestThroughRouter($request)
    {
        //將請求存入容器
        $this->app->instance('request', $request);
        //清除facade門面
        Facade::clearResolvedInstance('request');
        //初始化引導
        $this->bootstrap();
        //讓請求進入中間件
        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }

 kernel核心的sendRequestThroughRouter方法中最後的return 語句,便開始使用中間件來對系統接到的請求開始進行過濾了。ide

上一篇博文中,咱們提到了裝飾模式與laravel在此基礎上變種來的管道模式,即是在這裏開始運用。函數

從Pipeline對象一路跳轉,發現這個對象的基類爲laravel\vendor\laravel\framework\src\Illuminate\Pipeline\Pipeline.php。管道對象的構造方法把laravel系統容器傳入了其中。這裏再也不贅述管道模式的原理,直接來看關鍵方法then。

 1     public function then(Closure $destination)
 2     {
 3         //array_reduce — 用回調函數迭代地將數組簡化爲單一的值
 4         $pipeline = array_reduce(
 5             //array_reverse將數組反向排序,$this->carry()返回一個閉包函數用來迭代
 6             array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
 7         );
 8 
 9         return $pipeline($this->passable);
10     }
11 
12     protected function carry()
13     {
14         return function ($stack, $pipe) {
15             return function ($passable) use ($stack, $pipe) {
16                 if (is_callable($pipe)) {
17                     // If the pipe is an instance of a Closure, we will just call it directly but
18                     // otherwise we'll resolve the pipes out of the container and call it with
19                     // the appropriate method and arguments, returning the results back out.
20                     return $pipe($passable, $stack);
21                 } elseif (! is_object($pipe)) {
22                     //解析字符串
23                     list($name, $parameters) = $this->parsePipeString($pipe);
24 
25                     // If the pipe is a string we will parse the string and resolve the class out
26                     // of the dependency injection container. We can then build a callable and
27                     // execute the pipe function giving in the parameters that are required.
28                     //從容器中取出相應的對象
29                     $pipe = $this->getContainer()->make($name);
30                     $parameters = array_merge([$passable, $stack], $parameters);
31                 } else {
32                     // If the pipe is already an object we'll just make a callable and pass it to
33                     // the pipe as-is. There is no need to do any extra parsing and formatting
34                     // since the object we're given was already a fully instantiated object.
35                     $parameters = [$passable, $stack];
36                 }
37                 dd($parameters);
38                 //判斷中間件對象中是否存在handle方法,並將剛剛的$request對象與$stack閉包做爲參數,傳入中間件handle方法
39                 return method_exists($pipe, $this->method)
40                                 ? $pipe->{$this->method}(...$parameters)
41                                 : $pipe(...$parameters);
42             };
43         };
44     }

能夠看到,管道類中的其餘幾個方法都是很簡單的賦值方法。而then方法中,寫着的也是咱們上一次理解管道模式中用到的array_reduce函數,看到這個就親切多了。array_reverse($this->pipes)也就是將中間件數組逆向一下,由於管道模式的閉包運行時是像洋蔥同樣從外到內一層一層。而$this->prepareDestination($destination)這個方法則是以前then方法傳入的,經過中間件後,須要執行的業務代碼,也就是會開始調用控制器了。

關鍵代碼在$this->carry()中這個方法只看最外層的話,就是返回了一個閉包函數,至關於直接把這個閉包寫在array_reduce中。裏面那個閉包函數則是咱們比較熟悉的管道閉包寫法了,use的兩個參數分別表明本次閉包的執行結果與中間件數組中的一個參數。$passable變量則表明了$request對象。

這個閉包中,用了幾個if判斷,來肯定$pipe的值類型,也就是咱們的中間件數組的值中的類型,若是是閉包則直接運行,若是不是對象則從容器中加載出對象。若是剩下的都不是,則表明類型爲對象,直接構建參數,開始調用。還記得咱們的自定義中間件代碼都要寫在handle方法中嗎,這裏的$passable 和 $stack即是運行時傳入handle的變量$request 與 Closure $next了。而這裏的next方法又是什麼呢?

讓咱們打開路由中的管道文件vendor\laravel\framework\src\Illuminate\Routing\Pipeline.php看一看

 1     protected function carry()
 2     {
 3         return function ($stack, $pipe) {
 4             return function ($passable) use ($stack, $pipe) {
 5                 try {
 6                     //經過這個函數啓動剛剛的管道閉包
 7                     $slice = parent::carry();
 8                     //$stack, $pipe依然是閉包結果與中間件數組中的值
 9                     $callable = $slice($stack, $pipe);
10                     //$passable仍是request對象
11                     return $callable($passable);
12                 } catch (Exception $e) {
13                     return $this->handleException($passable, $e);
14                 } catch (Throwable $e) {
15                     return $this->handleException($passable, new FatalThrowableError($e));
16                 }
17             };
18         };
19     }

當這個管道閉包執行完畢以後,中間件暫時就結束了。便開始執行以前kernel核心傳入的$this->dispatchToRouter()方法了。

 如今來看一下路由經過中間件以後,是如何到達控制器的。dispatchToRouter方法通過一系列跳轉以後,到達了Router類的dispatchToRoute方法。其中findRoute方法獲取到了Routes對象集合(這個對象中包含了全部的路由對象,在以前介紹路由的章節時介紹過)並經過請求對象從中獲取了當前路由(這個查找過程用到了Collection基礎對象,因爲不是本文重點,暫時當成黑盒方法來看吧)。

    public function dispatchToRoute(Request $request)
    {
        return $this->runRoute($request, $this->findRoute($request));
    }

    //vendor\laravel\framework\src\Illuminate\Routing\Router.php
    protected function runRoute(Request $request, Route $route)
    {
        //將返回路由的閉包函數設置在request對象中
        $request->setRouteResolver(function () use ($route) {
            return $route;
        });
        //綁定事件監聽器
        $this->events->dispatch(new Events\RouteMatched($route, $request));

        return $this->prepareResponse($request,
            $this->runRouteWithinStack($route, $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) {
                            //將執行結果包裝成response響應對象
                            return $this->prepareResponse(
                                //進入控制器
                                $request, $route->run()
                            );
                        });
    }

    //vendor\laravel\framework\src\Illuminate\Routing\Route.php    
    public function run()
    {
        $this->container = $this->container ?: new Container;

        try {
            //以控制器形式執行
            if ($this->isControllerAction()) {
                return $this->runController();
            }
            //以閉包方式執行
            return $this->runCallable();
        } catch (HttpResponseException $e) {
            return $e->getResponse();
        }
    }

開頭咱們提到了路由加載的時候還會進行一次中間件過濾,就是在這裏了。在代碼執行到runRoute方法的時候,會再次進行中間件過濾,此次的中間件是寫在web數組中的系統中間件。而且此次經過中間件後,就會直接執行控制器,或是寫在路由閉包中的代碼了。返回的結果會被包裝成響應對象依次返回,最終返回到瀏覽器。

控制器方法是由controllerDispatcher類來啓動的這個類位於vendor\laravel\framework\src\Illuminate\Routing\ControllerDispatcher.php,它的dispatch方法接收了路由、控制器、方法三個參數。打印結果以下

這裏的controller中會有一個默認的middleware數組,是由於中間件也能夠在控制器中定義,不過咱們通常直接定義在kernel中了。

 再來看一下這段代碼具體作了什麼

 1     public function dispatch(Route $route, $controller, $method)
 2     {
 3         //解析依賴
 4         $parameters = $this->resolveClassMethodDependencies(
 5             //獲取路由中寫定的參數
 6             $route->parametersWithoutNulls(), $controller, $method
 7         );
 8         //判斷是否有callAction方法。實際上是在controller的父類中執行了call_user_func_array
 9         if (method_exists($controller, 'callAction')) {
10             return $controller->callAction($method, $parameters);
11         }
12         //若沒有則當成普通類進行調用返回
13         return $controller->{$method}(...array_values($parameters));
14     }

哦,差點忘了控制器的實例化是在Route類的runController方法中執行了getController

 1     public function getController()
 2     {
 3         if (! $this->controller) {
 4             //從路由中獲取控制器字符串
 5             $class = $this->parseControllerCallback()[0];
 6             //經過字符串分割make控制器實例
 7             $this->controller = $this->container->make(ltrim($class, '\\'));
 8         }
 9 
10         return $this->controller;
11     }
12 
13     protected function parseControllerCallback()
14     {
15         //laravel封裝的字符串操做類,route的action中記錄了咱們寫在route文件中的控制器相關字符串
16         return Str::parseCallback($this->action['uses']);
17     }

那麼,到這裏咱們又梳理了一次從中間件到控制器的流程。中間件這一章差很少就是這些內容了。

相關文章
相關標籤/搜索