摘要:中的中間件是中的一個(gè)重點(diǎn)本篇將從源碼的角度去講解中的中間件洞察中的中間件是如何運(yùn)行的明白為何我們使用中間件的時(shí)候要進(jìn)行那些步驟本篇文章假設(shè)讀者已經(jīng)掌握中間件的基本用法如果不了解其用法可以移步查看中間件的使用我們都知道使用中間件有三個(gè)步驟使用
Laravel中的中間件是laravel中的一個(gè)重點(diǎn),本篇將從源碼的角度去講解Lravel中的中間件,洞察Laravel中的中間件是如何運(yùn)行的,明白為何我們使用中間件的時(shí)候要進(jìn)行那些步驟. 本篇文章假設(shè)讀者已經(jīng)掌握中間件的基本用法,如果不了解其用法,可以移步查看laravel中間件的使用
我們都知道,使用Laravel中間件有三個(gè)步驟:
使用php artisan生成一個(gè)中間件,這里假設(shè)生成一個(gè)TestMiddleware的中間件
重寫TestMiddleware中的handle函數(shù),其中代碼邏輯寫在return $next($request);之前或者之后表示在執(zhí)行請(qǐng)求之前或者之后運(yùn)行這段代碼.
在app/Http/Kernel.php的routeMiddleware注冊(cè)一個(gè)中間件
然而,這上面的幾點(diǎn)下來(lái),你會(huì)不會(huì)一頭霧水,為什么要執(zhí)行這么些操作之后才能使用一個(gè)中間件.尤其是第二點(diǎn)?
中間件實(shí)現(xiàn)代碼你一定聽(tīng)過(guò)Laravel中間件的概念跟裝飾器模式很像.簡(jiǎn)單來(lái)講,裝飾器模式就是在開(kāi)放-關(guān)閉原則下動(dòng)態(tài)的增加或者刪除某一個(gè)功能.而Laravel的中間件也差不多是這個(gè)道理:
一個(gè)請(qǐng)求過(guò)來(lái),在執(zhí)行請(qǐng)求之前,可能要進(jìn)行Cookie加密,開(kāi)啟回話,CSRF保護(hù)等等操作.但是每一個(gè)請(qǐng)求不一定都需要這些操作,而且,在執(zhí)行請(qǐng)求之后也可能需要執(zhí)行一些操作.我們需要根據(jù)請(qǐng)求的特性動(dòng)態(tài)的增加一些操作.這些需求正好可以使用裝飾器模式解決.
但是,Laravel中的中間件在代碼實(shí)現(xiàn)上跟中間件 又有點(diǎn)區(qū)別,這里給出一段代碼.真實(shí)的模擬了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 "開(kāi)啟session
"; $next(); echo "關(guān)閉ession
"; } } class AddQueuedCookieToResponse implements Milldeware { public static function handle(Closure $next) { $next(); echo "添加下一次請(qǐng)求需要的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 "確定當(dāng)前程序是否處于維護(hù)狀態(tài)
"; $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 "請(qǐng)求向路由傳遞,返回相應(yīng)
"; }; $pipe = array_reverse($pipe); $callback = array_reduce($pipe,getSlice(),$firstSlice); call_user_func($callback); } then();
運(yùn)行代碼,輸出
確定當(dāng)前程序是否處于維護(hù)狀態(tài) 解密cookie 開(kāi)啟session 共享session中的Error變量 驗(yàn)證csrf Token 請(qǐng)求向路由傳遞,返回相應(yīng) 關(guān)閉ession 添加下一次請(qǐng)求需要的cookie 加密cookie
這段代碼可能有點(diǎn)難懂,原因在于對(duì)于閉包函數(shù)(Closure),array_reduce以及call_user_fun函數(shù),而且函數(shù)調(diào)用過(guò)程又是遞歸,可以嘗試使用xdebug來(lái)調(diào)試執(zhí)行.這里只提一點(diǎn),array_reduce中第二個(gè)參數(shù)是一個(gè)函數(shù),這個(gè)函數(shù)需要兩個(gè)參數(shù):
第一個(gè)參數(shù)從array_reduce的第一個(gè)參數(shù)$pipe數(shù)組中獲得
第二個(gè)參數(shù)為上一次調(diào)用的返回值.這個(gè)例子里面返回值是一個(gè)閉包函數(shù).
如果還是不懂可以看這個(gè)例子.當(dāng)理解這段代碼之后,你會(huì)發(fā)現(xiàn)使用Laravel中間件步驟中的第2步瞬間就明白了.
源碼解析好了,通過(guò)上面的代碼,我們已經(jīng)解決了第二個(gè)問(wèn)題.現(xiàn)在看看為什么使用中間件之前需要在app/Http/Kernel.php注冊(cè)中間件.
我們知道,所謂注冊(cè),也只是在$routeMiddleware數(shù)組中添加一項(xiàng)而已.
要想知道,為什么中間件注冊(cè)完之后就可以使用,我們需要從源碼的角度去看看Laravel在底層為我們做了什么.
從Index.php文件分析,這里我們不分析Laravel源碼的全部,只講解有關(guān)中間件的部分,關(guān)于Laravel的分析,請(qǐng)關(guān)注我的其他文章.并且,這里只講解全局中間件的運(yùn)行過(guò)程,因?yàn)槁酚芍虚g件和全局中間件的運(yùn)行流程是一樣的,但是路由中間件.涉及到路由分發(fā)過(guò)程,本次分析只專注于中間件,等到講解路由的時(shí)候再對(duì)路由中間件進(jìn)行展開(kāi)分析.
有關(guān)中間件的步驟從Index.php的handle函數(shù)開(kāi)始.
$kernel = $app->make(IlluminateContractsHttpKernel::class); $response = $kernel->handle( $request = IlluminateHttpRequest::capture() );
從這個(gè)函數(shù)開(kāi)始處理一個(gè)請(qǐng)求,注意這里kernel有一個(gè)繼承鏈,handle函數(shù)的真正實(shí)現(xiàn)在vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php文件中.
轉(zhuǎn)到Http/Kernel.php類,在看handle函數(shù)之前,我們發(fā)現(xiàn)這類有兩個(gè)protected的成員:$middleware跟$routeMiddleware.看到這個(gè)我們就可以聯(lián)想到我們注冊(cè)中間件的那個(gè)文件app/Http/Kernel.php,這個(gè)文件正是繼承Illuminate/Foundation/Http/Kernel.php的,所以我們明白了,當(dāng)我們?cè)?b>app/Http/Kernel.php注冊(cè)的全局中間件會(huì)在這里被處理.
接著,我們看handle函數(shù),調(diào)用了sendRequestThroughRouter函數(shù),進(jìn)入這個(gè)函數(shù),我們看到其函數(shù)體
{ $this->app->instance("request", $request); Facade::clearResolvedInstance("request"); $this->bootstrap(); //這里之后就會(huì)去處理中間件 return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter()); }
可以看到這你實(shí)例化了一個(gè)Pipeline.這個(gè)可以稱之為管道,如果懂得linux中的管道概念的話,那么就可以理解這里命名為Pipeline的原因:客戶端發(fā)過(guò)來(lái)的請(qǐng)求被一個(gè)又一個(gè)的中間件處理,前一個(gè)中間件處理往之后的結(jié)果交給了下一個(gè)中間價(jià),類似管道一樣.
pipeline之后調(diào)用了三個(gè)函數(shù)send, through, then.這三個(gè)函數(shù)分別做了
傳遞客戶端請(qǐng)求request到Pipeline對(duì)象
傳遞在app/Http/Kernel.php中定義的全局中間件到Pipeline對(duì)象
執(zhí)行中間件,其中then函數(shù)的參數(shù),$this->dispatchToRouter()返回的是一個(gè)回調(diào)函數(shù),這個(gè)函數(shù)可以類比為我們示例代碼中輸出請(qǐng)求向路由傳遞,返回相應(yīng)的函數(shù), 因?yàn)檫@里涉及到路由的工作流程,所以暫時(shí)這么理解,等到了分析路由的時(shí)候,我們?cè)倬C合起來(lái).
接下來(lái),我們來(lái)看then函數(shù)的代碼
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 ); }
發(fā)現(xiàn)then函數(shù)的代碼跟我們上面的示例代碼有點(diǎn)類似,其中 :
$pipe就是保存了在app/Http/Kernel.php中定義的全局中間件,具體邏輯可以看through函數(shù)
$this->passable中保存的是客戶端請(qǐng)求的實(shí)例對(duì)象requset.具體邏輯可以從send函數(shù)看到
而getInitialSlice調(diào)用的函數(shù)只是對(duì)原有的destination添加了一個(gè)$passable的參數(shù).這個(gè)$passabel就是請(qǐng)求實(shí)例.
protected function getInitialSlice(Closure $destination) { return function ($passable) use ($destination) { return call_user_func($destination, $passable); }; }
了解了then函數(shù)里的所有信息, 下面執(zhí)行的操作就跟我們上面的示例代碼一樣了,只要理解了上面代碼的邏輯,這里L(fēng)aravel的代碼也是這么工作的,唯一不同的地方在于getSlice返回的閉包函數(shù),從上面的例子可以知道返回的閉包函數(shù)才是調(diào)用中間件的核心,我們來(lái)看下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函數(shù)的大體邏輯跟我們上面實(shí)例代碼的邏輯差不多,只是我們上面調(diào)用在中間件的handle函數(shù)的時(shí)候直接使用$pipe::handle($stack);,因?yàn)橹虚g件里面的函數(shù)是靜態(tài)函數(shù).而在Laravel中,這里我們只是傳遞了要實(shí)例化的中間件的類名,所以在getSlice里面還要去實(shí)例化每個(gè)要執(zhí)行的中間件,
list($name, $parameters) = $this->parsePipeString($pipe); return call_user_func_array([$this->container->make($name), $this->method], array_merge([$passable, $stack], $parameters));
上面兩句代碼中,第一句根據(jù)中間件的類名去分離出要實(shí)例化的中間件類,和實(shí)例化中間件可能需要的參數(shù),
然后call_user_func_array里面由于柔和了幾行代碼,所以這里分解一下,函數(shù)包含兩個(gè)參數(shù) :
[$this->container->make($name), $this->method]為調(diào)用某個(gè)類中方法的寫法,其中$this->container->make($name)是使用服務(wù)容器去實(shí)例化要調(diào)用的中間件對(duì)象,$this->method就是handle函數(shù)
array_merge([$passable, $stack], $parameters)為調(diào)用中間件所需要的參數(shù),這里我們可以看到調(diào)用中間件的handle必然會(huì)傳遞兩個(gè)參數(shù):$passable(請(qǐng)求實(shí)例$request)和下一個(gè)中間件的回調(diào)函數(shù)$stack
到這里,Laravel中間件的部分就結(jié)束了,這部分代碼有點(diǎn)難以理解,尤其是一些具有函數(shù)式特性的函數(shù)調(diào)用,比如array_reduce,以及大量的閉包函數(shù)和遞歸調(diào)用,大家一定要耐心分析,可以多使用dd函數(shù)和xdebug工具來(lái)逐步分析.
這里扯一句,函數(shù)then里面可以看到有getSlice和$firstSlice的命名,這里slice是一片的意思,這里這樣子的命名方式是一種比喻 : 中間件的處理過(guò)程就上面講的類似管道,處理中間件的過(guò)程比作剝洋蔥,一個(gè)中間件的執(zhí)行過(guò)程就是剝一片洋蔥.
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/22126.html
摘要:原文發(fā)表在我的個(gè)人網(wǎng)站深入理解二中間操作流本篇教程是該系列教材的第二篇,將主要講述中中間操作流的概念。復(fù)雜用法示例下一步深入理解三模型間關(guān)系關(guān)聯(lián) 原文發(fā)表在我的個(gè)人網(wǎng)站:深入理解 Laravel Eloquent(二)——中間操作流(Builder) 本篇教程是該系列教材的第二篇,將主要講述 Eloquent 中中間操作流的概念。中間操作流是我自己總結(jié)并翻譯的概念,支撐該功能的類...
摘要:將請(qǐng)求傳入到指定的中間件路由。用于處理任務(wù)的方法接收兩個(gè)參數(shù),第一個(gè)是一個(gè)可傳遞的對(duì)象,第二個(gè)是閉包,在運(yùn)行最后一個(gè)管道后對(duì)象將被重定向到這個(gè)閉包。我希望這個(gè)實(shí)例能夠讓你對(duì)有更深如的了解,并知道如何使用它們。 這是一篇譯文,原文 Understanding Laravel Pipelines。譯文首發(fā)于 深入理解 Laravel 管道,轉(zhuǎn)載請(qǐng)注明出處。 基本上,你可以使用 larave...
摘要:前言年底了不太忙,最近一段時(shí)間也一直在研究,就想寫篇關(guān)于比較深一點(diǎn)的教程系列啥的,于是就找到站長(zhǎng)給開(kāi)了寫教程的渠道。優(yōu)點(diǎn)的就是為藝術(shù)家創(chuàng)造的框架,它也是工程化的趨勢(shì)。項(xiàng)目維護(hù)方便也是事實(shí)。如果有遇到問(wèn)題可以直接在教程下面留言。 前言 年底了不太忙,最近一段時(shí)間也一直在研究laravel,就想寫篇關(guān)于laravel比較深一點(diǎn)的教程系列啥的,于是就找到站長(zhǎng)給開(kāi)了寫教程的渠道。由于第一次寫,...
摘要:是什么是一個(gè),全稱為,翻譯為對(duì)象關(guān)系映射如果只把它當(dāng)成數(shù)組庫(kù)抽象層那就太小看它了。所謂對(duì)象,就是本文所說(shuō)的模型對(duì)象關(guān)系映射,即為模型間關(guān)系。至此,深入理解系列文章到此結(jié)束。 原文發(fā)表在我的個(gè)人網(wǎng)站:深入理解 Laravel Eloquent(三)——模型間關(guān)系(關(guān)聯(lián)) 在本篇文章中,我將跟大家一起學(xué)習(xí) Eloquent 中最復(fù)雜也是最難理解的部分——模型間關(guān)系。官方英文文檔中...
摘要:官方地址是目前最流行的框架,發(fā)展勢(shì)頭迅猛,應(yīng)用非常廣泛,有豐富的擴(kuò)展包可以應(yīng)付你能想到的各種應(yīng)用場(chǎng)景,框架思想前衛(wèi),跟隨時(shí)代潮流,提倡優(yōu)雅代碼,自稱為工匠,其中的模板引擎容器以及擴(kuò)展包為業(yè)務(wù)的開(kāi)發(fā)提供了極大的便利。 laravel5.5+ laravel官方地址 laravel是目前最流行的php框架,發(fā)展勢(shì)頭迅猛,應(yīng)用非常廣泛,有豐富的擴(kuò)展包可以應(yīng)付你能想到的各種應(yīng)用場(chǎng)景,lara...
閱讀 2658·2021-09-26 10:13
閱讀 6191·2021-09-08 10:46
閱讀 754·2019-08-30 15:53
閱讀 3027·2019-08-29 16:13
閱讀 2814·2019-08-26 12:23
閱讀 3540·2019-08-26 11:24
閱讀 1187·2019-08-23 18:09
閱讀 1085·2019-08-23 17:08