Laravel7——一文讀懂中介軟體原始碼

Ruma_z發表於2021-07-06

本文解決什麼問題

  1. 怎麼理解中介軟體,為什麼要中介軟體?
  2. 路由中介軟體和全域性中介軟體有什麼區別?
  3. 中介軟體的實現原理(原始碼解讀)

什麼是中介軟體

先貼出一張圖讓大家理解中介軟體

Laravel
再結合官方文件給出的定義:中介軟體提供了一種方便的機制來過濾進入應用程式的 HTTP 請求,就比較好理解了。

其實中介軟體也可以這麼理解①類似於設計模式的裝飾器模式,意義在於我們可以在不改變核心/原有的功能的情況下,為其新增其他的功能;②物件導向中抽象封裝的思想,將相同的功能全部抽象封裝起來;

現在我們再帶著第2、3問題,全域性與路由中介軟體的區別中介軟體的實現原理,下面來看原始碼分析。

中介軟體原始碼逐步解讀

生命週期的概括

Laravel程式的生命週期就是從客戶端接受到資料,然後處理資料返回一個響應的過程

Laravel 應用的所有請求入口都是 public/index.php 檔案。

// 檔案 public/index.php

// 1. 引入composer自動載入功能
require __DIR__.'/../vendor/autoload.php';
// 2. 例項化了服務容器$app
$app = require_once __DIR__.'/../bootstrap/app.php';
// 3. 從服務容器中
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
// 4. 將獲取的請求資料作為引數,例項化了一個Illuminate\Http\Request的例項;
//    簡單的可以理解為, 將$_GET $_POST $_SERVER 等php能獲取到的資料進行處理
$request = Illuminate\Http\Request::capture()
// 5. 處理 請求例項$request 得到 響應例項 $response
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

從上述的功能簡單可以看出,我們中介軟體的處理是在第5步,因為中介軟體其實就是對request請求進行處理的嘛

ps:此文著重講解中介軟體的實現,以下貼出的原始碼會對次要程式碼省略,也會淡化其他功能(如:服務容器,Facade),以方便大家理解;

中介軟體實現相關原始碼

// 檔案 vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
public function handle($request)
{
    try {
        $response = $this->sendRequestThroughRouter($request);
    } 
    return $response;
}
// 檔案 vendor/laravel/framework/src/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());
    }
// 檔案 vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php
// 管道類
class Pipeline implements PipelineContract
{

    protected $container;
    protected $passable;
    protected $pipes = [];
    protected $method = 'handle';

    public function __construct(Container $container = null)
    {
        $this->container = $container;
    }
    // 把$request例項放入到存到passable屬性內
    public function send($passable)
    {
        $this->passable = $passable;

        return $this;
    }
    // 把middleware存入到pipes屬性內
    public function through($pipes)
    {
        $this->pipes = is_array($pipes) ? $pipes : func_get_args();

        return $this;
    }

    public function then(Closure $destination)
    {
        $pipeline = array_reduce(
            array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
        );

        return $pipeline($this->passable);
    }

    protected function prepareDestination(Closure $destination)
    {
        return function ($passable) use ($destination) {
            try {
                return $destination($passable);
            } catch (Throwable $e) {
                return $this->handleException($passable, $e);
            }
        };
    }

    protected function carry()
    {
        return function ($stack, $pipe) {
            return function ($passable) use ($stack, $pipe) {
                try {
                    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];
                    }
                    $carry = method_exists($pipe, $this->method)
                                    ? $pipe->{$this->method}(...$parameters)
                                    : $pipe(...$parameters);

                    return $this->handleCarry($carry);
                } catch (Throwable $e) {
                    return $this->handleException($passable, $e);
                }
            };
        };
    }

}

以上就是從index入口檔案,到實現中介軟體功能的核心原始碼;從上面的第二段程式碼可以看出,中介軟體實現的核心功能很簡單,就是Pipeline例項的三個鏈式呼叫;所以著重分析,Pipeline裡面三個function做了什麼事,如何實現前後置中介軟體

send()/through()

其實不難看出,這兩個方法只是簡單的進行屬性設定。①把$request例項放入到存到passable屬性內;②把middleware中介軟體陣列存入到pipes屬性內。不用過多解釋

then()

由於篇幅限制,下面另起一文

手把手帶你理解中介軟體Class Pipeline - Function then()實現原理

路由中介軟體與全域性中介軟體

通過我們對then方法的瞭解,我們知道,then方法傳入的閉包$this->dispatchToRouter()是在前置全域性中介軟體和後置中介軟體的中間進行執行的。

那麼根據一層一層檢視呼叫最會發現

// 檔案 vendor/laravel/framework/src/Illuminate/Routing/Router.php
protected function runRouteWithinStack(Route $route, Request $request)
{
    ... ... 

    return (new Pipeline($this->container))
                    ->send($request)
                    ->through($middleware)
                    ->then(function ($request) use ($route) {
                            return $this->prepareResponse(
                                $request, $route->run()
                            );
                        });
    }

其實路由中介軟體的實現和全域性中介軟體的實現方式沒有區別… …

總結

新增中介軟體的整個過程,就是通過send方法傳入請求,用though方法傳入全域性中介軟體,最後用then方法實現了一個遞迴的閉包函式,最終執行的過程。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章