Laravel管道流原理強烈依賴array_reduce函數,咱們先來了解下array_reduce函數的使用。php
原標題PHP 內置函數 array_reduce 在 Laravel 中的使用laravel
在看array_reduce
在laravel
中的應用時,先來看看array_reduce
官方文檔是怎麼說的。segmentfault
array_reduce()
將回調函數callback
迭代地做用到array
數組中的每個單元中,從而將數組簡化爲單一的值。數組
mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )
array閉包
輸入的 array。app
callback函數
mixed callback ( mixed $carry , mixed $item )
$carry
包括上次迭代的值,若是本次迭代是第一次,那麼這個值是initial
,item
攜帶了本次迭代的值this
initialspa
若是指定了可選參數 initial,該參數將在處理開始前使用,或者當處理結束,數組爲空時的最後一個結果。code
從文檔說明能夠看出,array_reduce
函數是把數組的每一項,都經過給定的callback
函數,來簡化
的。
那咱們就來看看是怎麼簡化的。
$arr = ['AAAA', 'BBBB', 'CCCC']; $res = array_reduce($arr, function($carry, $item){ return $carry . $item; });
給定的數組長度爲3,故總迭代三次。
第一次迭代時 $carry = null $item = AAAA 返回AAAA
第一次迭代時 $carry = AAAA $item = BBBB 返回AAAABBBB
第一次迭代時 $carry = AAAABBBB $item = CCCC 返回AAAABBBBCCCC
這種方式將數組簡化爲一串字符串
AAAABBBBCCCC
$arr = ['AAAA', 'BBBB', 'CCCC']; $res = array_reduce($arr, function($carry, $item){ return $carry . $item; }, 'INITIAL-');
第一次迭代時($carry = INITIAL-),($item = AAAA) 返回INITIAL-AAAA
第一次迭代時($carry = INITIAL-AAAA),($item = BBBB), 返回INITIAL-AAAABBBB
第一次迭代時($carry = INITIAL-AAAABBBB),($item = CCCC),返回INITIAL-AAAABBBBCCCC
這種方式將數組簡化爲一串字符串
INITIAL-AAAABBBBCCCC
$arr = ['AAAA', 'BBBB', 'CCCC']; //沒帶初始值 $res = array_reduce($arr, function($carry, $item){ return function() use ($item){//這裏只use了item return strtolower($item) . '-'; }; });
第一次迭代時,$carry:null,$item = AAAA,返回一個use了$item = AAAA的閉包
第二次迭代時,$carry:use了$item = AAAA的閉包,$item = BBBB,返回一個use了$item = BBBB的閉包
第一次迭代時,$carry:use了$item = BBBB的閉包,$item = CCCC,返回一個use了$item = CCCC的閉包
這種方式將數組簡化爲一個閉包,即最後返回的
閉包
,當咱們執行這個閉包時$res()
獲得返回值CCCC-
上面這種方式只use ($item)
,每次迭代返回的閉包在下次迭代時,咱們都沒有用起來。只是又從新返回了一個use
了當前item
值的閉包。
$arr = ['AAAA']; $res = array_reduce($arr, function($carry, $item){ return function () use ($carry, $item) { if (is_null($carry)) { return 'Carry IS NULL' . $item; } }; });
注意,此時的數組長度爲1,而且沒有指定初始值
因爲數組長度爲1,故只迭代一次,返回一個閉包 use($carry = null, $item = 'AAAA')
,當咱們執行($res()
)這個閉包時,獲得的結果爲Carry IS NULLAAAA
。
接下來咱們從新改造下,
$arr = ['AAAA', 'BBBB']; $res = array_reduce($arr, function($carry, $item){ return function () use ($carry, $item) { if (is_null($carry)) { return 'Carry IS NULL' . $item; } if ($carry instanceof \Closure) { return $carry() . $item; } }; });
咱們新增了一個條件判斷,若當前迭代的值是一個閉包,返回該閉包的執行結果。
第一次迭代時,$carry
的值爲null
,$item
的值爲AAAA,返回一個閉包,
//僞代碼 function () use ($carry = null, $item = AAAA) { if (is_null($carry)) { return 'Carry IS NULL' . $item; } if ($carry instanceof \Closure) { return $carry() . $item; } }
假設咱們直接執行該閉包,將會返回Carry IS NULLAAAA
的結果。
第二次迭代時,$carry
的值爲上述返回的閉包(僞代碼
),$item
的值爲BBBB,返回一個閉包,
當咱們執行這個閉包時,知足
$carry instanceof \Closure
,獲得結果Carry IS NULLAAAABBBB
。
大體瞭解了array_reverse
函數的使用後,咱們來瞅瞅laravel
管道流裏使用array_reverse
的狀況。
我在Laravel中間件原理中有闡述,強烈建議先去看看Laravel中間件原理再回過頭來接着看。
php內置方法array_reduce把全部要經過的中間件都經過callback方法並壓縮爲一個Closure。最後在執行Initial
Laravel
中經過全局中間件的核心代碼以下:
//Illuminate\Foundation\Http\Kernel.php protected function sendRequestThroughRouter($request) { return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter()); } protected function dispatchToRouter() { return function ($request) { $this->app->instance('request', $request); return $this->router->dispatch($request); }; }
正如我前面說的,咱們發送一個$request
對象經過middleware
中間件數組,最後在執行dispatchToRouter
方法。
假設有兩個全局中間件,咱們來看看這兩個中間件是如何經過管道壓縮爲一個Closure
的。
Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class, App\Http\Middleware\AllowOrigin::class,//自定義中間件
IlluminatePipelinePipeline爲laravel的管道流核心類.
在Illuminate\Pipeline\Pipeline
的then
方法中,$destination
爲上述的dispatchToRouter
閉包,pipes
爲要經過的中間件數組,passable
爲Request
對象。
public function then(Closure $destination) { $pipeline = array_reduce( array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination) ); return $pipeline($this->passable); }
array_reverse
函數將中間件數組的每一項都經過$this->carry()
,初始值爲上述dispatchToRouter
方法返回的閉包。
protected function prepareDestination(Closure $destination) { return function ($passable) use ($destination) { return $destination($passable); }; } protected function carry() { return function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { if ($pipe instanceof Closure) { 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 $pipe->{$this->method}(...$parameters); }; }; }
第一次迭代時,返回一個閉包,use
了$stack
和$pipe
,$stack
的值爲初始值閉包,$pipe
爲中間件類名,此處是App\Http\Middleware\AllowOrigin::class
(注意array_reverse
函數把傳進來的中間件數組倒敘了)。
假設咱們直接運行該閉包,因爲此時$pipe
是一個String
類型的中間件類名,只知足! is_object($pipe)
這個條件,咱們將直接從容器中make
一個該中間件的實列出來,在執行該中間件實列的handle
方法(默認$this->method
爲handle
)。而且將request
對象和初始值做爲參數,傳給這個中間件。
public function handle($request, Closure $next) { //...... }
在這個中間件的handle
方法中,當咱們直接執行return $next($request)
時,至關於咱們開始執行array_reduce
函數的初始值閉包了,即上述的dispatchToRouter
方法返回的閉包。
protected function dispatchToRouter() { return function ($request) { $this->app->instance('request', $request); return $this->router->dispatch($request); }; }
好,假設結束。在第二次迭代時,也返回一個use
了$stack
和$pipe
,$stack
的值爲咱們第一次迭代時返回的閉包,$pipe
爲中間件類名,此處是Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class
。
兩次迭代結束,回到then
方法中,咱們手動執行了第二次迭代返回的閉包。
return $pipeline($this->passable);
當執行第二次迭代返回的閉包時,當前閉包use
的$pipe
爲Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class
,一樣只知足! is_object($pipe)
這個條件,咱們將會從容器中make
出CheckForMaintenanceMode
中間件的實列,在執行該實列的handle
方法,而且把第一次迭代返回的閉包做爲參數傳到handle
方法中。
當咱們在CheckForMaintenanceMode
中間件的handle
方法中執行return $next($request)
時,此時的$next
爲咱們第一次迭代返回的閉包,將回到咱們剛纔假設的流程那樣。從容器中make
一個App\Http\Middleware\AllowOrigin
實列,在執行該實列的handle
方法,並把初始值閉包做爲參數傳到AllowOrigin
中間件的handle方法中
。當咱們再在AllowOrigin
中間件中執行return $next($request)
時,表明咱們全部中間件都經過完成了,接下來開始執行dispatchToRouter
。
中間件是區分前後順序的,從這裏你應該能明白爲何要把中間件用array_reverse
倒敘了。
並非全部中間件在運行前都已經實例化了的,用到的時候纔去想容器取
中間件不執行$next($request)後續全部中間件沒法執行。
這篇文章是專們爲了上一篇Laravel中間件原理寫的,由於在寫Laravel中間件原理時我也不很清楚
array_reduce
在laravel
中的運行流程。若是有什麼不對的,歡迎指正。