理解Laravel中的pipeline
pipeline在laravel的啟動過程中出現次數很多,要了解laravel的啟動過程和生命周期,理解pipeline就是其中的一個關鍵點。網上對pipeline的講解很少,所以我自己寫一寫吧。
首先還是來看看調用棧,也就是從一個請求開始到返回響應,laravel都干了些什么。
在路由中配置如下
Route::get('/', function() { return debug_backtrace(); });
然后啟動laravel自帶的web server
php artisan serve

然后訪問localhost:8000/
可以看到打印的調用棧,很長……

左下角可以看出,從請求開始到結束,盡管什么都沒干,但是依然加載了39個類……那些追求卓越性能的同學們需要好好考慮一下……
我們的重點是pipeline,如果你按照上面的步驟看了,會發現有一個pipeline調用的次數非常多。
那它到底是做什么用的呢?
簡單一點講,它是為了實現Laravel中的middleware。仔細想想middleware的代碼
public function handle($request, Closure $next) { //do something for $request return $next($request); }
一個請求經歷層層的中間件的處理,才得到最終的請求,這到底是實現的呢?答案就是pipeline
首先來宏觀感受下pipeline,到底怎么用的,再去細說它

可以看到,主要有3個方法,也是pipeline暴露給使用者的所有方法
- send
- through
- then
send方法
/** * Set the object being sent through the pipeline. * * @param mixed $passable * @return $this */ public function send($passable) { $this->passable = $passable; return $this; }
從說明看就是被在pipeline中傳遞的對象
,可能不太好理解。那么我們就假定pipeline使用在middleware中,那么這個send的對象就是$request
想想看上面的中間件代碼,每個handle
方法接收的第一個參數是$request
,其實就是這兒設置的講被send的對象,它會在之后被到處傳遞,A處理完了送給B處理再送給C處理……pipeline,形象吧
through

顧名思義:通過、經由,就是上面send
設置的對象要經由哪些中間件來處理
上面截圖宏觀說明pipeline的用法的時候可以看到,在調用through的時候
(new Pipeline($this)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter());
看through
里傳的參數,很好懂,如果當前請求我們設置了不需要中間件,那么就傳一個空數組,不然就傳遞我們預先定義好的中間件數組
pipeline里的代碼如下
/** * Set the array of pipes. * * @param dynamic|array $pipes * @return $this */ public function through($pipes){ $this->pipes = is_array($pipes) ? $pipes : func_get_args(); return $this; }
這個看起來也很簡單,你要么傳一個數組,要么傳多個字符串,它會拼成一個數組,無所謂,反正指明中間件的名字就好
then
接下來就是核心,也是代碼晦澀的地方了。
then
方法就是當一切準備就緒后,下達開始處理的命令。
我們前面也能看到,send
和through
都是設置,并沒有做其他什么,所以當 要傳遞的對象、需要經歷的中間件都設置好之后,then
?當然就是開始處理咯!
還是先看看then
的代碼
/** * Run the pipeline with a final destination callback. * * @param \Closure $destination * @return mixed */ 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); ); }
看起來短短幾行,但是要理解起來可不太容易(這也是我比較喜歡laravel的原因,代碼簡單而優雅,一般人欣賞不來,哈哈)
我先大概講一下
then
方法需要接收一個匿名函數
$destination,至于什么樣的匿名函數,你先可以不用管,往后看。我一句一句解釋
public function then(Closure $destination){ //把傳進來的匿名函數包裝成一個閉包,不太理解沒關系,反正知道$firstSlice是一個函數就好了 $firstSlice = $this->getInitialSlice($destination); //把through方法傳進來的參數,也就是中間件數組反轉,為什么要這么做? //簡單講是利用棧的特性,繼續往下看就知道了 $pipes = array_reverse($this->pipes); //array_reduce(...)返回一個函數A,然后執行這個函數,把send進來的對象當參數傳遞函數A return call_user_func( array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable); ); }
然后呢?
沒有然后了,這就完成了整個工作,是不是很優雅(坑爹)的代碼,哈哈
那我們的精力就放到這個array_reduce
上面,它到底返回了一個什么神奇的函數,我只要把$request傳遞給它,就過完了所有中間件?
array_reduce($arr, 'funcA',5)
大致等價于,第二個參數可以是函數名的字符串
function fake_array_reduce($arr, $func, $initial=null) $temp = $initial; foreach($arr as $v) { $temp = $func($temp, $v); } return $temp; }
理解意思即可
回到then
的源碼,代碼中傳遞的第二個參數一個是一個函數,而它是通過執行$this->getSlice()
獲得的一個函數,我們來看看這個getSlice
方法
/** * Get a Closure that represents a slice of the application onion. * * @return \Closure */ protected function getSlice(){ return function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { 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) ); } }; }; }
剛看到這里我也有點頭暈,這閉包套閉包的,什么鬼!
我們在簡化一下,就是調用$this->getSlice()
之后得到了一個函數,假設是函數B
,函數B
接收兩個參數,并返回一個函數C
我們來大致模擬一下這個過程
寫一段假代碼,僅僅是為了更好地說明這個過程:
function haha($stack, $middleware) { return function($param) use ($stack, $middleware){ //do something }; } function fake_array_reduce($middlewares, $func, $inital=null) { $temp = $initial; //假設傳遞進來的函數$func = 'haha' foreach($middlewares as $middleware) { $temp = $func($temp, $middleware); } return $temp; }
這個過程的基本流程如上,haha
返回了一個空函數(實際上肯定不是),我只是為了說明每次都返回一個函數作為下一次迭代的參數,而并沒有執行任何其它功能,按照上面的代碼,最后執行完fake_array_reduce
會得到一個函數,這個函數接收一個參數。
所以再回過頭來看
call_user_func(array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable);
實際上就是相當于調用了array_reduce
最后一次迭代返回的函數,然后給這個函數傳遞了一個參數$this->passable
所以,這時候我們需要關注最后一次返回的函數是什么,看看源碼關鍵是這段
return function($stack, $pipe) { return function($passable) use ($stack, $pipe) { if($pipe instanceof Closure) { return call_user_func($pipe, $passable, $stack); } else { //.. } } }
每次迭代傳入了當前的$pipe和上一次迭代返回的函數

由上圖所示
由于把pipes先反轉了一下,所以最后一次迭代array_reduce
得到的函數f3
所use的是($stack=f2, $pipe=$middleware[0])
那么call_user_func(f3,$this->passable)
相當于是
return call_user_func($middleware[0]->handle, $this->passable, f2);
仔細想想,middleware里面的handle方法
public function handle($request, Closure $next) { //... return $next($request); }
在這里就相當于就是return f2($request)
也就相當于return call_user_func($middleware[1]->handle, $request, f1)
而這個$request是經過middleware[0]處理過后傳遞過來的。
……
這個過程比較繞,需要大家自己再畫圖理解一下,比如源代碼里用到的onion(洋蔥),$stack(棧),這些單詞可以幫助大家更好地理解這個過程。
所以return call_user_func($pipes, $this->getSlice(), $this->passable)
就會把$request
請求按middleware定義的先后順序,流水線處理,中間件處理完了之后再傳遞給$this->dispatchToRouter()
這個方法處理(這又是一個抽象),從而得到最后的響應結果。
理解pipeline需要理解 閉包 棧等概念,熟悉了pipeline會對理解php的新特性有比較大的幫助。