在閱讀laravel源碼過程中,在Illuminate\Foudation\Http\Kenel.php中,開始處理requst請求中有這么一段代碼
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
透過字面理解知道,這段代碼是實例化一個Pipeline(流水線),然后依次將本次請求的$requst對象,交給中間件middleware處理,之后再給路由器轉發處理這個請求。
在laravel的文檔中,我們也知道處理請求的這樣一個過程,請求會先經過我們定義的中間件,然后再通過路由解析,交給具體的controller處理,這樣其實就像是一個流水線的過程,$request對象依次的在各個工作臺上被處理,每個工作臺都把自己處理完的$request對象交給下一個工作臺。
但是在閱讀 Pipeline 類的時候,還是遇到的困難,經過近一小時的努力理解和手動嘗試,最終徹底弄懂了,覺得這幾行代碼非常有趣,值得記錄一下。
代碼理解
new Pipleline($this->app)
將laravel容積對象傳遞給pipeline-
pipeline->send($request)
public function send($passable) { $this->passable = $passable; return $this; }
設置pipeline中的 $passable為本次請求的request對象
-
pipeline->through()
public function through($pipes) { $this->pipes = is_array($pipes) ? $pipes : func_get_args(); return $this; }
將kenel.php中定義的的middleware數組傳遞給pipeline中的pipes
-
pipeline->then()
public function then(Closure $destination) { $pipeline = array_reduce( array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination) ); return $pipeline($this->passable); }
這個方法中的array_reduce是最關鍵的操作,在此之前,我對這個數組的方法僅僅是講過的層面,平時開發中從沒用過。
在這里,laravel用這個方法實現了一個流水線,具體的流程如下:- 首先我們需要了解下 array_reduce 的作用和使用方法,
-
方法定義 :array_reduce ( array
$array
, [callable]$callback
, [mixed]$initial
=null
) : [mixed] - 作用: 將回調函數 callback 迭代地作用到 array 數組中的每一個單元中,從而將數組簡化為單一的值
$arr = [2, 4, 6, 8]; $result = array_reduce($arr, function($stack, $pipe) { // $stack就是上次遍歷數組上一個元素時,對這個元素處理之后return的值, // 當遍歷第一個數組中的第一個元素時,如果array_reduce第三個參數為null時,$stack 就是空,否則$stack最開始就是第三個參數的值 return $stack + $pipe; }, 10); echo $result.PHP_EOL; // echo 30
-
方法定義 :array_reduce ( array
- 應用到代碼中的then方法中,array_reduce有三個參數
- array_reverse($pipes)
即kenel中定義的中間件的逆序。為什么要逆序呢,是因為第二個參數返回的是一個方法,該方法內部每次都會把前一次的結果壓到棧中,導致最終執行的順序是倒著的,所以先把$pipe倒序一下,就能保證后邊順序執行了 - $this->carry()
protected function carry() { return function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { if (is_callable($pipe)) { return $pipe($passable, $stack); } elseif (! is_object($pipe)) { list($name, $parameters) = $this->parsePipeString($pipe); $pipe = $this->getContainer()->make($name); $parameters = array_merge([$passable, $stack], $parameters); } else { $parameters = [$passable, $stack]; } return method_exists($pipe, $this->method) ? $pipe->{$this->method}(...$parameters) : $pipe(...$parameters); }; }; }
但仔細閱讀我們發現,在這個閉包中,每次都把$stack作為第二個參數,交給$pipe()或者$pipe->handle()方法,在middleware的handle方法中,我們知道他定義是handle($request, Closure $next)。
遍歷第一個middleware后,$stack就是carry最內層的方法,也就被當作了下一個中間件的$next參數,所以在每個中間件的最后,我們執行$next($request)
,其實就是把這個中間件處理過的$request交給了之前的那個中間件。
這一塊比較繞,我按照這個思路寫了一個小demo,利于理解class middleware1 { function handle($request, Closure $next) { echo "middleware 1, request=".$request.PHP_EOL; $request = $request."-1"; return $next($request); } } class middleware2 { function handle($request, Closure $next) { echo "middleware 2, request=".$request.PHP_EOL; $request = $request."-2"; return $next($request); } } class middleware3 { function handle($request, Closure $next) { echo "middleware 3, request=".$request.PHP_EOL; $request = $request."-3"; return $next($request); } } const METHOD = "handle"; $pipes = [ new middleware1(), new middleware2(), new middleware3() ]; function carry() { return function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { $params = [$passable, $stack]; return $pipe->{METHOD}(...$params); }; }; } $pipeline = array_reduce(array_reverse($pipes), carry(), function($passable) { echo "final passable, final request=".$passable.PHP_EOL; }); $pipeline("request"); // middleware 1, request=request // middleware 2, request=request-1 // middleware 3, request=request-1-2 // final passable, final request=request-1-2-3
- array_reverse($pipes)
- this->prepareDestination($destination))
第三個參數返回值也是一個匿名函數, 這樣能保證最后一個middleware中執行$next(request,把流水線繼續下去,否則最后一個中間件中的$next就會失敗了
- 首先我們需要了解下 array_reduce 的作用和使用方法,
總結
自己水平有限,寫的有些亂,大家可以通過執行一下上邊的那個小demo,參照laravel源碼,應該能理解的更好