Laravel 管道流原理

Laravel管道流原理強烈依賴array_reduce函數,咱們先來了解下array_reduce函數的使用。php

原標題PHP 內置函數 array_reduce 在 Laravel 中的使用laravel

array_reduce

在看array_reducelaravel中的應用時,先來看看array_reduce官方文檔是怎麼說的。segmentfault

array_reduce() 將回調函數 callback 迭代地做用到 array 數組中的每個單元中,從而將數組簡化爲單一的值。數組

mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] )
  1. array閉包

輸入的 array。app

  1. callback函數

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

  1. initialspa

若是指定了可選參數 initial,該參數將在處理開始前使用,或者當處理結束,數組爲空時的最後一個結果。code

從文檔說明能夠看出,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,//自定義中間件

IlluminatePipelinePipeline爲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中的運行流程。若是有什麼不對的,歡迎指正。

相關文章
相關標籤/搜索