摘要:中的中間件是中的一個重點本篇將從源碼的角度去講解中的中間件洞察中的中間件是如何運行的明白為何我們使用中間件的時候要進行那些步驟本篇文章假設讀者已經掌握中間件的基本用法如果不了解其用法可以移步查看中間件的使用我們都知道使用中間件有三個步驟使用
Laravel中的中間件是laravel中的一個重點,本篇將從源碼的角度去講解Lravel中的中間件,洞察Laravel中的中間件是如何運行的,明白為何我們使用中間件的時候要進行那些步驟. 本篇文章假設讀者已經掌握中間件的基本用法,如果不了解其用法,可以移步查看laravel中間件的使用
我們都知道,使用Laravel中間件有三個步驟:
使用php artisan生成一個中間件,這里假設生成一個TestMiddleware的中間件
重寫TestMiddleware中的handle函數,其中代碼邏輯寫在return $next($request);之前或者之后表示在執行請求之前或者之后運行這段代碼.
在app/Http/Kernel.php的routeMiddleware注冊一個中間件
然而,這上面的幾點下來,你會不會一頭霧水,為什么要執行這么些操作之后才能使用一個中間件.尤其是第二點?
中間件實現代碼你一定聽過Laravel中間件的概念跟裝飾器模式很像.簡單來講,裝飾器模式就是在開放-關閉原則下動態的增加或者刪除某一個功能.而Laravel的中間件也差不多是這個道理:
一個請求過來,在執行請求之前,可能要進行Cookie加密,開啟回話,CSRF保護等等操作.但是每一個請求不一定都需要這些操作,而且,在執行請求之后也可能需要執行一些操作.我們需要根據請求的特性動態的增加一些操作.這些需求正好可以使用裝飾器模式解決.
但是,Laravel中的中間件在代碼實現上跟中間件 又有點區別,這里給出一段代碼.真實的模擬了Laravel中間件的工作流程.
"; $next(); } } class ShowErrorsFromSession implements Milldeware { public static function handle(Closure $next) { echo "共享session中的Error變量
"; $next(); } } class StartSession implements Milldeware { public static function handle(Closure $next) { echo "開啟session
"; $next(); echo "關閉ession
"; } } class AddQueuedCookieToResponse implements Milldeware { public static function handle(Closure $next) { $next(); echo "添加下一次請求需要的cookie
"; } } class EncryptCookies implements Milldeware { public static function handle(Closure $next) { echo "解密cookie
"; $next(); echo "加密cookie
"; } } class CheckForMaintenacceMode implements Milldeware { public static function handle(Closure $next) { echo "確定當前程序是否處于維護狀態
"; $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 "請求向路由傳遞,返回相應
"; }; $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(IlluminateContractsHttpKernel::class); $response = $kernel->handle( $request = IlluminateHttpRequest::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是一片的意思,這里這樣子的命名方式是一種比喻 : 中間件的處理過程就上面講的類似管道,處理中間件的過程比作剝洋蔥,一個中間件的執行過程就是剝一片洋蔥.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/22126.html
摘要:原文發表在我的個人網站深入理解二中間操作流本篇教程是該系列教材的第二篇,將主要講述中中間操作流的概念。復雜用法示例下一步深入理解三模型間關系關聯 原文發表在我的個人網站:深入理解 Laravel Eloquent(二)——中間操作流(Builder) 本篇教程是該系列教材的第二篇,將主要講述 Eloquent 中中間操作流的概念。中間操作流是我自己總結并翻譯的概念,支撐該功能的類...
摘要:將請求傳入到指定的中間件路由。用于處理任務的方法接收兩個參數,第一個是一個可傳遞的對象,第二個是閉包,在運行最后一個管道后對象將被重定向到這個閉包。我希望這個實例能夠讓你對有更深如的了解,并知道如何使用它們。 這是一篇譯文,原文 Understanding Laravel Pipelines。譯文首發于 深入理解 Laravel 管道,轉載請注明出處。 基本上,你可以使用 larave...
摘要:前言年底了不太忙,最近一段時間也一直在研究,就想寫篇關于比較深一點的教程系列啥的,于是就找到站長給開了寫教程的渠道。優點的就是為藝術家創造的框架,它也是工程化的趨勢。項目維護方便也是事實。如果有遇到問題可以直接在教程下面留言。 前言 年底了不太忙,最近一段時間也一直在研究laravel,就想寫篇關于laravel比較深一點的教程系列啥的,于是就找到站長給開了寫教程的渠道。由于第一次寫,...
摘要:是什么是一個,全稱為,翻譯為對象關系映射如果只把它當成數組庫抽象層那就太小看它了。所謂對象,就是本文所說的模型對象關系映射,即為模型間關系。至此,深入理解系列文章到此結束。 原文發表在我的個人網站:深入理解 Laravel Eloquent(三)——模型間關系(關聯) 在本篇文章中,我將跟大家一起學習 Eloquent 中最復雜也是最難理解的部分——模型間關系。官方英文文檔中...
摘要:官方地址是目前最流行的框架,發展勢頭迅猛,應用非常廣泛,有豐富的擴展包可以應付你能想到的各種應用場景,框架思想前衛,跟隨時代潮流,提倡優雅代碼,自稱為工匠,其中的模板引擎容器以及擴展包為業務的開發提供了極大的便利。 laravel5.5+ laravel官方地址 laravel是目前最流行的php框架,發展勢頭迅猛,應用非常廣泛,有豐富的擴展包可以應付你能想到的各種應用場景,lara...
閱讀 2571·2021-09-26 10:13
閱讀 5969·2021-09-08 10:46
閱讀 686·2019-08-30 15:53
閱讀 2957·2019-08-29 16:13
閱讀 2750·2019-08-26 12:23
閱讀 3478·2019-08-26 11:24
閱讀 1085·2019-08-23 18:09
閱讀 1028·2019-08-23 17:08