Laravel管道流原理強烈依賴array_reduce函式,我們先來了解下array_reduce函式的使用。
原標題PHP 內建函式 array_reduce 在 Laravel 中的使用
array_reduce
array_reduce()
將回撥函式callback
迭代地作用到array
陣列中的每一個單元中,從而將陣列簡化為單一的值。
mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )
-
array
輸入的 array。
-
callback
mixed callback ( mixed $carry , mixed $item )
$carry
包括上次迭代的值,如果本次迭代是第一次,那麼這個值是initial
,item
攜帶了本次迭代的值 - initial
如果指定了可選引數 initial,該引數將在處理開始前使用,或者當處理結束,陣列為空時的最後一個結果。
從文件說明可以看出,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
值的閉包。
閉包USE閉包
$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
。
Laravel中的array_reverse
大致瞭解了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,//自定義中介軟體
Illuminate\Pipeline\Pipeline為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
中的執行流程。如果有什麼不對的,歡迎指正。