Laravel中的中間件是laravel中的一個重點,本篇將從源碼的角度去講解Lravel中的中間件,洞察Laravel中的中間件是如何運行的,明白爲什麼咱們使用中間件的時候要進行那些步驟. 本篇文章假設讀者已經掌握中間件的基本用法,若是不瞭解其用法,能夠移步查看laravel中間件的使用php
咱們都知道,使用Laravel中間件有三個步驟:linux
使用php artisan
生成一箇中間件,這裏假設生成一個TestMiddleware
的中間件laravel
重寫TestMiddleware
中的handle
函數,其中代碼邏輯寫在return $next($request);
以前或者以後表示在執行請求以前或者以後運行這段代碼.bootstrap
在app/Http/Kernel.php
的routeMiddleware
註冊一箇中間件數組
然而,這上面的幾點下來,你會不會一頭霧水,爲何要執行這麼些操做以後才能使用一箇中間件.尤爲是第二點?cookie
你必定聽過Laravel中間件的概念跟裝飾器模式很像.簡單來說,裝飾器模式就是在開放-關閉原則下動態的增長或者刪除某一個功能.而Laravel的中間件也差很少是這個道理:session
一個請求過來,在執行請求以前,可能要進行Cookie加密,開啓回話,CSRF保護等等操做.可是每個請求不必定都須要這些操做,並且,在執行請求以後也可能須要執行一些操做.咱們須要根據請求的特性動態的增長一些操做.這些需求正好可使用裝飾器模式解決.閉包
可是,Laravel中的中間件在代碼實現上跟中間件 又有點區別,這裏給出一段代碼.真實的模擬了Laravel中間件的工做流程.app
<?php /** * Created by PhpStorm. * User: 89745 * Date: 2016/12/4 * Time: 13:56 */ interface Milldeware { public static function handle(Closure $next); } class VerfiyCsrfToekn implements Milldeware { public static function handle(Closure $next) { echo '驗證csrf Token <br>'; $next(); } } class ShowErrorsFromSession implements Milldeware { public static function handle(Closure $next) { echo '共享session中的Error變量 <br>'; $next(); } } class StartSession implements Milldeware { public static function handle(Closure $next) { echo '開啓session <br>'; $next(); echo '關閉ession <br>'; } } class AddQueuedCookieToResponse implements Milldeware { public static function handle(Closure $next) { $next(); echo '添加下一次請求須要的cookie <br>'; } } class EncryptCookies implements Milldeware { public static function handle(Closure $next) { echo '解密cookie <br>'; $next(); echo '加密cookie <br>'; } } class CheckForMaintenacceMode implements Milldeware { public static function handle(Closure $next) { echo '肯定當前程序是否處於維護狀態 <br>'; $next(); } } function getSlice() { return function($stack,$pipe) { return function() use($stack,$pipe){ return $pipe::handle($stack); }; }; } function then() { $pipe = [ 'CheckForMaintenacceMode', 'EncryptCookies', 'AddQueuedCookieToResponse', 'StartSession', 'ShowErrorsFromSession', 'VerfiyCsrfToekn' ]; $firstSlice = function() { echo '請求向路由傳遞,返回相應 <br>'; }; $pipe = array_reverse($pipe); $callback = array_reduce($pipe,getSlice(),$firstSlice); call_user_func($callback); } then();
運行代碼,輸出函數
肯定當前程序是否處於維護狀態 解密cookie 開啓session 共享session中的Error變量 驗證csrf Token 請求向路由傳遞,返回相應 關閉ession 添加下一次請求須要的cookie 加密cookie
這段代碼可能有點難懂,緣由在於對於閉包函數(Closure),array_reduce
以及call_user_fun
函數,並且函數調用過程又是遞歸,能夠嘗試使用xdebug來調試執行.這裏只提一點,array_reduce
中第二個參數是一個函數,這個函數須要兩個參數:
第一個參數從array_reduce
的第一個參數$pipe
數組中得到
第二個參數爲上一次調用的返回值.這個例子裏面返回值是一個閉包函數.
若是仍是不懂能夠看這個例子.當理解這段代碼以後,你會發現使用Laravel中間件步驟中的第2步瞬間就明白了.
好了,經過上面的代碼,咱們已經解決了第二個問題.如今看看爲何使用中間件以前須要在app/Http/Kernel.php
註冊中間件.
咱們知道,所謂註冊,也只是在$routeMiddleware
數組中添加一項而已.
要想知道,爲何中間件註冊完以後就可使用,咱們須要從源碼的角度去看看Laravel在底層爲咱們作了什麼.
從Index.php文件分析,這裏咱們不分析Laravel源碼的所有,只講解有關中間件的部分,關於Laravel的分析,請關注個人其餘文章.而且,這裏只講解全局中間件的運行過程,由於路由中間件和全局中間件的運行流程是同樣的,可是路由中間件.涉及到路由分發過程,本次分析只專一於中間件,等到講解路由的時候再對路由中間件進行展開分析.
有關中間件的步驟從Index.php的handle函數開始.
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); $response = $kernel->handle( $request = Illuminate\Http\Request::capture() );
從這個函數開始處理一個請求,注意這裏kernel有一個繼承鏈,handle函數的真正實如今vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
文件中.
轉到Http/Kernel.php
類,在看handle
函數以前,咱們發現這類有兩個protected
的成員:$middleware
跟$routeMiddleware
.看到這個咱們就能夠聯想到咱們註冊中間件的那個文件app/Http/Kernel.php
,這個文件正是繼承Illuminate/Foundation/Http/Kernel.php
的,因此咱們明白了,當咱們在app/Http/Kernel.php
註冊的全局中間件會在這裏被處理.
接着,咱們看handle函數,調用了sendRequestThroughRouter
函數,進入這個函數,咱們看到其函數體
{ $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()); }
能夠看到這你實例化了一個Pipeline
.這個能夠稱之爲管道,若是懂得linux中的管道概念的話,那麼就能夠理解這裏命名爲Pipeline
的緣由:客戶端發過來的請求被一個又一個的中間件處理,前一箇中間件處理往以後的結果交給了下一個中間價,相似管道同樣.
pipeline
以後調用了三個函數send, through, then
.這三個函數分別作了
傳遞客戶端請求request
到Pipeline
對象
傳遞在app/Http/Kernel.php
中定義的全局中間件到Pipeline
對象
執行中間件,其中then函數的參數,$this->dispatchToRouter()
返回的是一個回調函數,這個函數能夠類比爲咱們示例代碼中輸出請求向路由傳遞,返回相應
的函數, 由於這裏涉及到路由的工做流程,因此暫時這麼理解,等到了分析路由的時候,咱們再綜合起來.
接下來,咱們來看then
函數的代碼
public function then(Closure $destination) { $firstSlice = $this->getInitialSlice($destination); $pipes = array_reverse($this->pipes); return call_user_func( array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable ); }
發現then函數的代碼跟咱們上面的示例代碼有點相似,其中 :
$pipe
就是保存了在app/Http/Kernel.php
中定義的全局中間件,具體邏輯能夠看through
函數
$this->passable
中保存的是客戶端請求的實例對象requset
.具體邏輯能夠從send
函數看到
而getInitialSlice
調用的函數只是對原有的destination
添加了一個$passable
的參數.這個$passabel
就是請求實例.
protected function getInitialSlice(Closure $destination) { return function ($passable) use ($destination) { return call_user_func($destination, $passable); }; }
瞭解了then函數裏的全部信息, 下面執行的操做就跟咱們上面的示例代碼同樣了,只要理解了上面代碼的邏輯,這裏Laravel的代碼也是這麼工做的,惟一不一樣的地方在於getSlice
返回的閉包函數,從上面的例子能夠知道返回的閉包函數纔是調用中間件的核心,咱們來看下getSlice
究竟是怎麼工做的
protected function getSlice() { return function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { // If the pipe is an instance of a Closure, we will just call it directly but // otherwise we'll resolve the pipes out of the container and call it with // the appropriate method and arguments, returning the results back out. if ($pipe instanceof Closure) { return call_user_func($pipe, $passable, $stack); } else { list($name, $parameters) = $this->parsePipeString($pipe); return call_user_func_array([$this->container->make($name), $this->method], array_merge([$passable, $stack], $parameters)); } }; }; }
getSlice
函數的大致邏輯跟咱們上面實例代碼的邏輯差很少,只是咱們上面調用在中間件的handle函數的時候直接使用$pipe::handle($stack);
,由於中間件裏面的函數是靜態函數.而在Laravel中,這裏咱們只是傳遞了要實例化的中間件的類名,因此在getSlice
裏面還要去實例化每一個要執行的中間件,
list($name, $parameters) = $this->parsePipeString($pipe); return call_user_func_array([$this->container->make($name), $this->method], array_merge([$passable, $stack], $parameters));
上面兩句代碼中,第一句根據中間件的類名去分離出要實例化的中間件類,和實例化中間件可能須要的參數,
而後call_user_func_array
裏面因爲柔和了幾行代碼,因此這裏分解一下,函數包含兩個參數 :
[$this->container->make($name), $this->method]
爲調用某個類中方法的寫法,其中$this->container->make($name)
是使用服務容器去實例化要調用的中間件對象,$this->method
就是handle函數
array_merge([$passable, $stack], $parameters)
爲調用中間件所須要的參數,這裏咱們能夠看到調用中間件的handle必然會傳遞兩個參數:$passable
(請求實例$request)和下一個中間件的回調函數$stack
到這裏,Laravel中間件的部分就結束了,這部分代碼有點難以理解,尤爲是一些具備函數式特性的函數調用,好比array_reduce
,以及大量的閉包函數和遞歸調用,你們必定要耐心分析,能夠多使用dd
函數和xdebug
工具來逐步分析.
這裏扯一句,函數then
裏面能夠看到有getSlice
和$firstSlice
的命名,這裏slice
是一片的意思,這裏這樣子的命名方式是一種比喻 : 中間件的處理過程就上面講的相似管道,處理中間件的過程比做剝洋蔥,一箇中間件的執行過程就是剝一片洋蔥.