說明
上一篇分析了應用的初始化,也就是對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;
新增到一個佇列。最終的解析結果大概是這樣的:
至此,全域性中介軟體就載入完畢。