ThinkPHP6 原始碼閱讀(四):載入中介軟體

tsin發表於2019-08-14

說明

上一篇分析了應用的初始化,也就是對Http類的run()方法裡面呼叫的runWithRequest ()方法的第一行程式碼$this->initialize()的展開分析。讓我們再看一眼runWithRequest ()方法的前幾行:

protected function runWithRequest(Request $request)
{
    $this->initialize();

    // 載入全域性中介軟體
    if (is_file($this->app->getBasePath() . 'middleware.php')) {
        $this->app->middleware->import(include $this->app->getBasePath() . 'middleware.php');
    }
.
.
.

應用初始化後,接下來開始處理中介軟體。

中介軟體類的初始化

依然是百用不厭的套路,通過$this->app->middleware來例項化中介軟體並獲取其例項。從Middleware類中可看到,該類中有一個__make方法——在ThinkPHP的容器實現中,該方法會最先被呼叫。__make方法程式碼如下:

 public static function __make(App $app, Config $config)
{
    return (new static($config->get('middleware')))->setApp($app);
}

該方法有兩個依賴類「App」和「Config」,通過ThinkPHP的容器,我們不費吹灰之力——僅僅在方法的引數新增型別約束的引數——就獲得了兩個方法所依賴的例項,$config$app
該方法首先傳入「middleware」配置,例項化自身,然後呼叫setApp方法。setApp方法很簡單:

public function setApp(App $app)
{
    $this->app = $app;
    return $this;
}

僅僅是將$app例項儲存起來,最後返回Middleware類自身的一個例項。

執行完__make方法後,整個例項化Middleware類的流程回到make方法內,最終返回Middleware類的例項。

匯入中介軟體

通過$this->app->middleware得到Middleware類的例項後,接著程式呼叫import方法,傳入從「app」目錄下的「middleware.php」檔案中讀取的資料。該檔案的原始內容如下(原來全部註釋掉的):

return [
    // 全域性請求快取
    // \think\middleware\CheckRequestCache::class,
    // 多語言載入
     \think\middleware\LoadLangPack::class,
    // Session初始化
    // \think\middleware\SessionInit::class,
    // 頁面Trace除錯
     \think\middleware\TraceDebug::class,
];

這裡為了研究中介軟體是如何載入的,先去掉兩個註釋,也就是新增兩個中介軟體。接下來看import方法:

public function import(array $middlewares = [], string $type = 'route'): void
{
    foreach ($middlewares as $middleware) {
        $this->add($middleware, $type);
    }
}

該方法傳入一箇中介軟體的陣列和一箇中介軟體型別,預設為route,關鍵是裡面的add方法。跳到add方法:

public function add($middleware, string $type = 'route'): void
{
    if (is_null($middleware)) {
        return;
    }

    $middleware = $this->buildMiddleware($middleware, $type);

    if ($middleware) {
        $this->queue[$type][] = $middleware;
    }
}

實際上真正幹活的是buildMiddleware方法,直接前往:

protected function buildMiddleware($middleware, string $type = 'route'): array
{
    // 是否是陣列
    if (is_array($middleware)) {
        // 列出中介軟體及其引數
        // 這裡說明我們可以給中介軟體傳入引數,且形式為 [中介軟體, 引數]
        list($middleware, $param) = $middleware;
    }
    // 是否是一個閉包
    // 說明中介軟體可以是一個閉包
    if ($middleware instanceof \Closure) {
        //返回閉包和引數
        return [$middleware, $param ?? null];
    }
    // 排除了上面幾種型別,且不是字串,丟擲錯誤
    if (!is_string($middleware)) {
        throw new InvalidArgumentException('The middleware is invalid');
    }

    //檢查「$config」成員變數中是否有別名,有則解析出來
    if (isset($this->config[$middleware])) {
        $middleware = $this->config[$middleware];
    }

    //如果中介軟體有包含中介軟體(說明中介軟體可以巢狀)
    //再走一遍「import」遞迴解析
    if (is_array($middleware)) {
        $this->import($middleware, $type);
        return [];
    }
    //返回解析結果
    return [[$middleware, 'handle'], $param ?? null];
}

詳細分析見以上程式碼註釋。最後返回的結果,在add方法中,執行$this->queue[$type][] = $middleware;新增到一個佇列。最終的解析結果大概是這樣的:
ThinkPHP6 原始碼閱讀(四):穿越中介軟體
至此,全域性中介軟體就載入完畢。

Was mich nicht umbringt, macht mich stärker

相關文章