Laravel 管道流原理

godruoyi發表於2017-07-07

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 ] )
  1. array

    輸入的 array。

  2. callback

    mixed callback ( mixed $carry , mixed $item )
    $carry包括上次迭代的值,如果本次迭代是第一次,那麼這個值是 initialitem 攜帶了本次迭代的值

  3. initial

    如果指定了可選引數 initial,該引數將在處理開始前使用,或者當處理結束,陣列為空時的最後一個結果。

從文件說明可以看出,array_reduce函式是把陣列的每一項,都通過給定的callback函式,來簡化的。

那我們就來看看是怎麼簡化的。

$arr = ['AAAA', 'BBBB', 'CCCC'];

$res = array_reduce($arr, function($carry, $item){
    return $carry . $item;
});

給定的陣列長度為3,故總迭代三次。

  1. 第一次迭代時 $carry = null $item = AAAA 返回AAAA
  2. 第一次迭代時 $carry = AAAA $item = BBBB 返回AAAABBBB
  3. 第一次迭代時 $carry = AAAABBBB $item = CCCC 返回AAAABBBBCCCC

這種方式將陣列簡化為一串字串AAAABBBBCCCC

帶初始值的情況

$arr = ['AAAA', 'BBBB', 'CCCC'];

$res = array_reduce($arr, function($carry, $item){
    return $carry . $item;
}, 'INITIAL-');
  1. 第一次迭代時($carry = INITIAL-),($item = AAAA) 返回INITIAL-AAAA
  2. 第一次迭代時($carry = INITIAL-AAAA),($item = BBBB), 返回INITIAL-AAAABBBB
  3. 第一次迭代時($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) . '-';
    };
});
  1. 第一次迭代時,$carry:null,$item = AAAA,返回一個use了$item = AAAA的閉包
  2. 第二次迭代時,$carry:use了$item = AAAA的閉包,$item = BBBB,返回一個use了$item = BBBB的閉包
  3. 第一次迭代時,$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\Pipelinethen方法中,$destination為上述的dispatchToRouter閉包,pipes為要通過的中介軟體陣列,passableRequest物件。

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->methodhandle)。並且將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$pipeIlluminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,同樣只滿足! is_object($pipe)這個條件,我們將會從容器中makeCheckForMaintenanceMode中介軟體的實列,在執行該實列的handle方法,並且把第一次迭代返回的閉包作為引數傳到handle方法中。

當我們在CheckForMaintenanceMode中介軟體的handle方法中執行return $next($request)時,此時的$next為我們第一次迭代返回的閉包,將回到我們剛才假設的流程那樣。從容器中make一個App\Http\Middleware\AllowOrigin實列,在執行該實列的handle方法,並把初始值閉包作為引數傳到AllowOrigin中介軟體的handle方法中。當我們再在AllowOrigin中介軟體中執行return $next($request)時,代表我們所有中介軟體都通過完成了,接下來開始執行dispatchToRouter

  1. 中介軟體是區分先後順序的,從這裡你應該能明白為什麼要把中介軟體用array_reverse倒敘了。
  2. 並不是所有中介軟體在執行前都已經例項化了的,用到的時候才去想容器取
  3. 中介軟體不執行$next($request)後續所有中介軟體無法執行。

這篇文章是專們為了上一篇Laravel中介軟體原理寫的,因為在寫Laravel中介軟體原理時我也不很清楚array_reducelaravel中的執行流程。如果有什麼不對的,歡迎指正。

二楞徐的閒談雜魚

相關文章