【Laravel-海賊王系列】第七章,Pipeline 類解析

Pipeline (管道)

Laravel 的中間件是經過管道類來實現的。laravel

經過內核處理請求的過程當中管道的做用來解析管道類!json

protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request);

    Facade::clearResolvedInstance('request');

    $this->bootstrap();

    return (new Pipeline($this->app)) // "這是個 Illuminate\Routing\Pipeline 對象,繼承了 Illuminate\Pipeline\Pipeline 對象。"
                ->send($request) // "調用 Illuminate\Pipeline\Pipeline 的 send() 方法傳入 $request 對象"
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) // "傳入須要通過的中間件數組"
                ->then($this->dispatchToRouter());// "傳入最後執行的閉包而且運行管道"
}
複製代碼

接下來咱們看看這段代碼是如何讓請求經過全部的中間件以後返回的。bootstrap

代碼調用追蹤

  1. 約定 (new Pipeline($this->app)) 下面統稱 $pipe數組

  2. $pipe->send($request) // 將 $request 對象賦值給 $pipe->passablebash

  3. $pipe->pipes 的賦值閉包

array:5 [▼
    0 => "App\Http\Middleware\CheckForMaintenanceMode"
    1 => "Illuminate\Foundation\Http\Middleware\ValidatePostSize"
    2 => "App\Http\Middleware\TrimStrings"
    3 => "Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull"
    4 => "App\Http\Middleware\TrustProxies"
]
$pipe->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
複製代碼

4.$pipe->then($this->dispatchToRouter()); 這裏是執行父類的 then() 方法app

public function then(Closure $destination)
{
    $pipeline = array_reduce(
        array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
    );
    return $pipeline($this->passable);
}
複製代碼

array_reverse($this->pipes),就是將剛纔存入的中間件順序反轉。函數

$this->carry() 這裏的 $this 指向的對象是 Illuminate\Routing\Pipeline 對象所以調用 carry() 方法是自身的。ui

$this->prepareDestination($destination) 返回一個閉包this

return function ($passable) use ($destination) {
    return $destination($passable);
};
複製代碼

接着開始看

$pipeline = array_reduce(
    array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
);
複製代碼

這段代碼能夠改形成容易讀的方式

$cb    = $this->carry();
 
 $stack = $this->prepareDestination($destination);
 
 foreach (array_reverse($this->pipes) as $pipe) {
     $stack = $cb($stack,$pipe);
 }
 
 $pipeline = $stack;
複製代碼

先獲取一個閉包,而後獲取第一個閉包參數 $stack ,以後遍歷 pipes 數組來進行迭代,每次迭代會更新下次迭代的 $stack 變量,等迭代完成以後將 $stack 賦值給 $pipeline.

因此咱們只要關心最後 $pipeline 拿到的是一個什麼東西 那麼這裏就要解析 $this->carry() 每次執行以後返回的是什麼,下面是執行調用的方法。

protected function carry()
{
    return function ($stack, $pipe) {
        return function ($passable) use ($stack, $pipe) {
            try {
                $slice = parent::carry();
                $callable = $slice($stack, $pipe);
                return $callable($passable);
            } catch (Exception $e) {
                return $this->handleException($passable, $e);
            } catch (Throwable $e) {
                return $this->handleException($passable, new FatalThrowableError($e));
            }
        };
    };
}
複製代碼

這裏其實每次執行返回的就是個新閉包,同時 $stack,$pipe 的值也隨着調用存入閉包。爲了方便我聲明下變量

$cb = function ($passable) use ($stack, $pipe) {
    try {
        $slice = parent::carry();
        $callable = $slice($stack, $pipe);
        return $callable($passable);
    } catch (Exception $e) {
        return $this->handleException($passable, $e);
    } catch (Throwable $e) {
        return $this->handleException($passable, new FatalThrowableError($e));
    }
};
複製代碼

因此上面 $cb 的值就是 $this->carry() 執行後返回的閉包就像洋蔥同樣,咱們來看封裝過程。

第一次封裝 $stack1 = $cb($passable) use ($this->prepareDestination($destination),'App\Http\Middleware\TrustProxies')
第二次封裝 $stack2 = $cb($passable) use ($stack1,'Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull')
第三次封裝 $stack3 = $cb($passable) use ($stack2,'App\Http\Middleware\TrimStrings')
第四次封裝 $stack4 = $cb($passable) use ($stack3,'Illuminate\Foundation\Http\Middleware\ValidatePostSize')
第五次封裝 $stack5 = $cb($passable) use ($stack4,'App\Http\Middleware\CheckForMaintenanceMode')
複製代碼

最後 $pipeline 對象實際就是 $stack5

看到這裏咱們獲取了一個層層封裝的閉包,同時咱們也看出爲何中間件的順序先反轉了,由於執行的時候是從 $stack5 開始的!那麼下一步就是看看如何執行了。

return $pipeline($this->passable);
複製代碼

在遞歸完成以後咱們得到了一個 $pipeline 對象, 此時咱們觸發這個閉包,後面就是連鎖反應!這裏我用 $stack5 來代替 $pipeline 方便理解。 首先執行

$stack5($this->passable,'App\Http\Middleware\CheckForMaintenanceMode')
複製代碼

這段代碼是一個起點,也就是點燃整個連鎖反應的開始,咱們來追蹤下去會回到 $cb 這個閉包的邏輯,

$cb = function ($passable) use ($stack, $pipe) {
    try {
        $slice = parent::carry();
        $callable = $slice($stack, $pipe);
        return $callable($passable);
    } catch (Exception $e) {
        return $this->handleException($passable, $e);
    } catch (Throwable $e) {
        return $this->handleException($passable, new FatalThrowableError($e));
    }
};
複製代碼

這裏最終仍是調用了 parent::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)) {
                [$name, $parameters] = $this->parsePipeString($pipe);
                $pipe = $this->getContainer()->make($name);

                $parameters = array_merge([$passable, $stack], $parameters);
            } else {
                $parameters = [$passable, $stack];
            }
            $response = method_exists($pipe, $this->method)
                ? $pipe->{$this->method}(...$parameters)
                : $pipe(...$parameters);

            return $response instanceof Responsable
                ? $response->toResponse($this->container->make(Request::class))
                : $response;
        };
    };
}
複製代碼

到這裏咱們已經進入最後的堡壘,因爲傳入的 $pipe 是中間件的名稱,不是閉包因此進入 elseif 中開始執行。 第一次執行:

$stack5 = $cb($passable) use ($stack4,'App\Http\Middleware\CheckForMaintenanceMode')  
複製代碼
function ($passable) use ($stack, $pipe) {
    if (is_callable($pipe)) {
        return $pipe($passable, $stack);
    } elseif (!is_object($pipe)) {
        // "進入這裏開始執行"
        [$name, $parameters] = $this->parsePipeString($pipe);
        
        // "從經過Application對象從容器中生產對應的類, 這裏不拓展了,就是應用了容器的特性來生產類。"
        $pipe = $this->getContainer()->make($name); 
        
        // "這裏很是重要,將 $passable (就是開始的 $request 對象) 和 $stack (就是最近一次調用的$stack4) 合併成數組"
        $parameters = array_merge([$passable, $stack], $parameters);
    } else {
        $parameters = [$passable, $stack];
    }
    
    // "調用中間件中$pipe->handle($request, $stack4)"
    $response = method_exists($pipe, $this->method)
        ? $pipe->{$this->method}(...$parameters)
        : $pipe(...$parameters);

    return $response instanceof Responsable
        ? $response->toResponse($this->container->make(Request::class))
        : $response;
};
複製代碼

分析完上面並無完成,最後代碼運行到

// "默認配置,能夠經過 $this->via($method) 來修改。"
$this->method = 'handle';

 // "...$parameters 解構數組參數實際調用 $pipe->handle($request, $stack4)"
$response = method_exists($pipe, $this->method)
            ? $pipe->{$this->method}(...$parameters)
            : $pipe(...$parameters);
複製代碼

此時只是調用一次閉包,那麼以前封裝了那麼多層都怎麼辦呢?

接下來咱們分析 CheckForMaintenanceMode 中間件的 handle($request, Closure $next) 方法。

public function handle($request, Closure $next)
{
    if ($this->app->isDownForMaintenance()) {
        $data = json_decode(file_get_contents($this->app->storagePath().'/framework/down'), true);

        if (isset($data['allowed']) && IpUtils::checkIp($request->ip(), (array) $data['allowed'])) {
            return $next($request);
        }

        if ($this->inExceptArray($request)) {
            return $next($request);
        }

        throw new MaintenanceModeException($data['time'], $data['retry'], $data['message']);
    }

    return $next($request);
}
複製代碼

return $next($request); 這句話點亮了一切

實際調用了 $stack4($request) , 咱們來看看當時 $stack4 這個閉包裏面是啥

$stack4 = $cb($passable) use ($stack3,'Illuminate\Foundation\Http\Middleware\ValidatePostSize')
複製代碼

是否是和 $stack5 有點像, 直到這裏造成了遞歸, 同時解答了爲何中間件的格式要按照文檔上面說用。

回到最初的封裝

第一次封裝 $stack1 = $cb($passable) use ($this->prepareDestination($destination),'App\Http\Middleware\TrustProxies')
第二次封裝 $stack2 = $cb($passable) use ($stack1,'Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull')
第三次封裝 $stack3 = $cb($passable) use ($stack2,'App\Http\Middleware\TrimStrings')
第四次封裝 $stack4 = $cb($passable) use ($stack3,'Illuminate\Foundation\Http\Middleware\ValidatePostSize')
第五次封裝 $stack5 = $cb($passable) use ($stack4,'App\Http\Middleware\CheckForMaintenanceMode')
複製代碼

咱們的調用鏈就變成了

$stack5 = $cb($passable) use ($stack4,'App\Http\Middleware\CheckForMaintenanceMode')
$stack4 = $cb($passable) use ($stack3,'Illuminate\Foundation\Http\Middleware\ValidatePostSize')
$stack3 = $cb($passable) use ($stack2,'App\Http\Middleware\TrimStrings')
$stack2 = $cb($passable) use ($stack1,'Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull')
$stack1 = $cb($passable) use ($this->prepareDestination($destination),'App\Http\Middleware\TrustProxies')
複製代碼

最後執行

$this->prepareDestination($destination)

$destination = $this->dispatchToRouter();

// "返回一個 $response 對象 ..."
return function ($passable) use ($destination) {
    return $destination($passable);
}; 
    
複製代碼

總結

到這裏管道的核心代碼就結束了,

固然是經過在內核啓動週期中

關於請求發送到路由獲取響應這個實例來解析。

laravel 中路由對系統的管道作了細微的拓展,

總體仍是沒啥變化,就是閉包套閉包,不停地調用,就像剝洋蔥。

相關文章
相關標籤/搜索