管道過濾器通過之前的三篇文章,我們已經(jīng)學(xué)習(xí)完了服務(wù)容器相關(guān)的內(nèi)容,可以說,服務(wù)容器就是整個 Laravel 框架的靈魂,從啟動的第一步開始就是創(chuàng)建容器并且加載所有的服務(wù)對象。而說起管道,其實(shí)大家也不會太陌生,在程序開發(fā)的世界中,管道模式的應(yīng)用隨處可見,同樣在 Laravel 框架中,它也是核心一般的存在。甚至可以說,管道和服務(wù)容器的組合,才讓我們有了一個這樣的框架可以使用。 什么是管道前面說過,管道模式非常常見,為什么這么說呢?
常見不?經(jīng)常用吧?這個 Linux 命令就是一個管道命令。前面一條命令的結(jié)果交給后面一條命令來執(zhí)行,就像一條管道一樣讓這個命令請求的結(jié)果向下流動,這就是管道模式的應(yīng)用。 除了這個你還能想到什么呢?如果你跟過我的 PHP 設(shè)計模式系列的話,那么 責(zé)任鏈模式 很明顯就是管道模式在 面向?qū)ο?語言中的應(yīng)用呀。 管道模式一般是和過濾器一起使用的,什么是過濾器呢?其實(shí)就是我們要處理請求的那些中間方法,比如說上面命令中的 grep ,或者是 wc 、awk 這些的命令。大家其實(shí)很快就能發(fā)現(xiàn),在 Laravel 框架中,我們的中間件就是一個個的過濾器。而我們要處理的數(shù)據(jù),就是那個 Request 請求對象。 Laravel 中管道的加載應(yīng)用還記得我們在服務(wù)容器中看到過的一個 sendRequestThroughRouter() 方法嗎?另外在最早講中間件時,我們也講過這里,我們再來看看它的代碼。
在這段代碼中,最后返回的那個 Pipeline 對象就是一個管道對象。我們來看看它的這幾個方法是什么意思。
構(gòu)造函數(shù)、send() 和 through() 方法都比較簡單,就是給當(dāng)前的對象中的屬性賦值,這個沒什么特別的。不過在 Pipeline 對象中,所有的方法都是會 return 一個 $this ,其實(shí)也就是實(shí)現(xiàn)了對象的鏈?zhǔn)秸{(diào)用。 重點(diǎn)在于 then() 方法。
這個方法也出乎意料的簡單吧?里面只用了一個 array_reduce() ,OK,到這里,你就可以和面試官吹牛了,Laravel 中的管道,或者說中間件,其實(shí)最核心的就是這個 array_reduce() 方法。要搞清楚 then() 方法是在干什么,我們就要先搞明白 array_reduce() 是在干嘛。 array_reducearray_reduce() 這個函數(shù)在官方文檔的簽名是這樣的:
它的作用是將回調(diào)函數(shù) callback 迭代地作用到 array 數(shù)組中的每一個單元中,從而將數(shù)組簡化為單一的值。如果指定了可選參數(shù) initial,該參數(shù)將用作處理開始時的初始值,如果數(shù)組為空,則會作為最終結(jié)果返回。 callback 這個回調(diào)函數(shù)會有兩個參數(shù),分別是 carry 攜帶上次迭代的返回值,如果迭代是第一次,那么這個值就是 initial 。另一個參數(shù)是 item ,也就是數(shù)組中的每個值。 看不懂吧?正常,我也看不懂,別慌,看例子。
這段代碼是官網(wǎng)上的例子。我們定義了一個 sum() 方法用于累加,另外再定義了一個 product() 方法用于階乘。前兩段測試的結(jié)果可以看出,通過將第一個數(shù)組傳遞進(jìn)去,然后調(diào)用 sum() 方法,我們完成了累加的功能,輸出了一個唯一的結(jié)果值。第二段則是增加了第三個參數(shù)給了個默認(rèn)的 10 ,結(jié)果就是多乘了一個 10 的累乘結(jié)果。而最后一段則是一個空的數(shù)組,返回的是 initial 給定的結(jié)果。 框架中 array_reduce 的參數(shù)搞清楚了 array_reduce() 我們再回來看看框架源碼中給出的參數(shù)。第一個參數(shù)是使用 array_reverse() 返回之后的 pipes 里面的內(nèi)容,這個 pipes 是我們通過 through() 方法傳遞進(jìn)來的。再回到 Kernel 中,我們會發(fā)現(xiàn)這個方法傳遞進(jìn)去的參數(shù)正是我們框架中加載的中間件 $middleware 成員變量。 之前的 bootstrap() 過程中,我們已經(jīng)將所有的 app/Http/Kernel.php 中注冊的中間件綁定注冊到了服務(wù)容器中。因此,這個 pipes 數(shù)組中,就是我們所有的中間件信息。 接下來第二個參數(shù)是調(diào)用的一個 carry() 函數(shù),它在 array_reduce() 方法中代表的是 callback 那個回調(diào)函數(shù)。
這個方法就復(fù)雜許多了。我們一步步的來看。 參數(shù)不用多說了吧,stack 是上一次的返回值,pipe 是當(dāng)前我們要處理的值,也就是當(dāng)前的中間件對象。在這個回調(diào)函數(shù)中又調(diào)用了一層回調(diào)函數(shù),并將這兩個值通過 use 傳遞進(jìn)去。而在里面的這個回調(diào)函數(shù)中,我們的參數(shù)是 passable 這個變量。這個 passable 又是哪里來的?別急,我們先看這個函數(shù)內(nèi)部的實(shí)現(xiàn),最后會再說到 passable 這個問題。 進(jìn)入函數(shù)內(nèi)部的 try 代碼段中,第一個判斷,如果 pipe 是一個回調(diào)函數(shù),直接調(diào)用它并返回;第二個判斷,如果 pipe 不是一個對象而是一個 string 的話,解構(gòu) pipe 信息,服務(wù)容器 make 它,并且準(zhǔn)備好參數(shù);最后一個 else 也就是 pipe 是一個對象,那么將 passable 和 stack 作為它的參數(shù)。最后,如果對象都有了,就會統(tǒng)一調(diào)用對象的 handle 方法,這個方法名也就是 $this->method 屬性定義的方法名。在最底下 $carry 調(diào)用對象或者回調(diào)函數(shù)的執(zhí)行方法。handle 熟悉不?我們自定義中間件時,要實(shí)現(xiàn)的就是這個方法。參考:【Laravel系列3.4】中間件在路由與控制器中的應(yīng)用 https://mp.weixin.qq.com/s/9340q7F_hKrrxgf4o1LNMw。 最終返回的就是這個 $carry 變量,它是啥?中間件中 return next() 的東西呀,管道中的下一個回調(diào)函數(shù)。 上面的代碼我們是嵌套了兩層的回調(diào)函數(shù),通過之間的學(xué)習(xí),我們知道回調(diào)函數(shù)是有延遲加載的特性的,也就說,這一堆代碼是在我們最終調(diào)用這個回調(diào)函數(shù)的時候才會觸發(fā)的,那么它是在什么時候調(diào)用的呢?
沒錯,then() 方法最后的這個 return 這里,現(xiàn)在知道 passable 是從哪里傳遞進(jìn)去的了吧。注意,這個 passable 和最后那個默認(rèn) initial 參數(shù),都是我們當(dāng)前的請求 Request 對象和路由 Route 對象。也就是說,在整個 Laravel 框架中,我們管道中流動的,正是我們的 Request 對象,而最后返回的,則是各個中間件以及控制器處理完成之后的 Response 對象。中間件、控制器甚至路由,其實(shí)都是我們管道中的一個個的過濾器,根據(jù)我們的條件情況以及業(yè)務(wù)情況,可以隨時中斷或者對請求進(jìn)行處理,這下也就理解了什么我們可以在中間件返回,也可以在路由直接返回頁面結(jié)果了吧。 好吧,學(xué)習(xí)一個管道,其實(shí)我們又把整個請求響應(yīng)流程梳理了一遍。收獲滿滿吧! 直接寫一個管道應(yīng)用來測試直接調(diào)試管道可能比較復(fù)雜,因?yàn)?Laravel 框架加載的內(nèi)容非常多,不過我們可以自己寫一個管道應(yīng)用來測試,并且可以設(shè)置斷點(diǎn)來方便地調(diào)試。 首先,我們需要定義幾個過濾器,也就是我們的中間件啦,不過我們不需要去實(shí)現(xiàn) Laravel 規(guī)范的,只需要有 handle() 方法就可以了。
沒有什么特殊的功能,我們過濾掉 Email 中的 @ 符號變成 # 號,這個很多網(wǎng)站有會這樣的功能,避免被爬取 Email 地址。另外兩個就是增加符號和時間戳。在 AddTime 的處理中,我們使用的是 后置 中間件的功能,也就是在中間件完成處理后再添加內(nèi)容。這個在中間件相關(guān)的課程中我們也已經(jīng)講過了。 接下來,就是使用管道來進(jìn)行處理。
在這段測試代碼中,我們對 pipes 數(shù)組使用了類字符串、實(shí)例對象、回調(diào)函數(shù)三種方式來實(shí)現(xiàn)中間件過濾器,可以看到最后的輸出結(jié)果正是我們想要的內(nèi)容。 大家可以在這里設(shè)置斷點(diǎn)然后進(jìn)入到 Pipeline 中查看這些中間件是如何調(diào)用運(yùn)行的,為什么要使用 array_reverse() 反轉(zhuǎn)中間件的順序,為什么后置中間件會在最后才去添加數(shù)據(jù)內(nèi)容。這一塊的調(diào)試就留給大家自己來吧! 總結(jié)服務(wù)容器、管道(中間件)可以說是 Laravel 框架中最最核心的內(nèi)容,也可以說整個框架就是建立在這兩個模式之下的。對于服務(wù)容器的理解,就是要解決類的依賴問題,而對于管道的理解,則是要解決請求和響應(yīng)的數(shù)據(jù)流問題。本身我們做 Web 開發(fā),實(shí)際上就是在做對請求和響應(yīng)這兩條數(shù)據(jù)流的各種操作而已。 理解了最核心的兩部分內(nèi)容之后,下篇文章的課程中我們再來看看在 Laravel 中非常常用的 門面 功能是怎樣實(shí)現(xiàn)的。 參考文檔: Laravel 中的 Pipeline — 管道設(shè)計范式 :https:///laravel/t/7543/pipeline-pipeline-design-paradigm-in-laravel Laravel 管道流原理 :https:///articles/5206/the-use-of-php-built-in-function-array-reduce-in-laravel |
|