中介軟體
從請求過程中可以看出,第一步就是載入的中介軟體。那麼如何載入的呢?看下面這段程式碼
$this->app->middleware
讓 app 例項訪問屬性 middleware?你會發現例項中並沒有這個屬性,那麼訪問一個不存在的屬性會發生什麼呢?它會去訪問 __get 魔術方法,你有這個想法之後會在 Container 中發現這個魔術方法,最終它會去 make 建立物件,對於 make 的過程請到 解析 Request
章節檢視。
建立 middleware 的過程中有一個細節,就是載入配置檔案 middleware.php,進入到 think\Middleware 檔案之後,你會發現這麼一段程式碼。
public static function __make(App $app, Config $config)
{
return (new static($config->get('middleware')))->setApp($app);
}
正如之前所提到的那樣,使用 make 方法建立物件會執行預設方法 __make,所以這個時候就會將 config 中的 middleware.php 載入進來。目前這個版本似乎沒有加入這個配置檔案,你可以手動新增。
來看下面一段程式碼,從這段程式碼引出中介軟體的載入過程。
$this->app->middleware->import(include $this->app->getBasePath() . 'middleware.php');
在整個請求過程,首先載入的是 app/middleware.php 檔案中的中介軟體,預設提供了四個中介軟體。但是這些都是不載入,你可自行選擇開啟。然後來看看 import 如何載入的。
public function import(array $middlewares = [], string $type = 'route'): void
{
foreach ($middlewares as $middleware) {
$this->add($middleware, $type);
}
}
$type
引數標記了路由的型別,目前只看到 route
和 controller
兩種型別。預設全域性中介軟體是 route
型別的。add
方法才是實實在在的匯入。來看看的這個方法做了哪些事兒。
public function add($middleware, string $type = 'route'): void
{
// null 直接返回
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;
}
// 如果是 middleware 是閉包, 直接返回
if ($middleware instanceof \Closure) {
return [$middleware, $param ?? null];
}
// 不支援非字串的型別
if (!is_string($middleware)) {
throw new InvalidArgumentException('The middleware is invalid');
}
// 支援鍵值對,正如官方手冊中提到,例如這樣 [ 'hello' => middleware ]
if (isset($this->config[$middleware])) {
$middleware = $this->config[$middleware];
}
// 當你從配置檔案解析出來是陣列格式,就遞迴呼叫
if (is_array($middleware)) {
$this->import($middleware, $type);
return [];
}
// 最後返回一個 middware 的類名和預設方法 'handle'
// handle 方法對中介軟體而言是必須的,不然無法執行
return [[$middleware, 'handle'], $param ?? null];
}
然後返回會加入到 Queue 佇列中,這個時候你開啟全域性中介軟體的其中一個話,佇列中將會是這樣的內容。
array(1) {
["route"]=>
array(1) {
[0]=>
array(2) {
[0]=>
array(2) {
[0]=>
string(34) "think\middleware\CheckRequestCache"
[1]=>
string(6) "handle"
}
[1]=>
NULL
}
}
}
這個和我們預期是一樣的,在程式碼解釋過程也沒有遇到任何阻礙。我們繼續往下看,這裡僅僅是加入的過程,在後面請求執行的時候來看看如何執行的這些中介軟體的。
中介軟體執行
中介軟體的是由 Diapatch
方法執行,而核心在 resolve
方法,來看一下這個方法做了什麼。
protected function resolve(string $type = 'route')
{
return function (Request $request) use ($type) {
$middleware = array_shift($this->queue[$type]);
if (null === $middleware) {
throw new InvalidArgumentException('The queue was exhausted, with no response returned');
}
// 1
list($call, $param) = $middleware;
// 2
if (is_array($call) && is_string($call[0])) {
$call = [$this->app->make($call[0]), $call[1]];
}
try {
// 3
$response = $this->app->invoke($call, [$request, $this->resolve($type), $param]);
} catch (HttpResponseException $exception) {
$response = $exception->getResponse();
}
if (!$response instanceof Response) {
throw new LogicException('The middleware must return Response instance');
}
return $response;
};
}
按照 1,2,3 的步驟進行說明。
佇列使用了 array_shift
來進行,說明會執行先進來的中介軟體。所以你可以在到達控制器之前做很多業務應用初始化的事情。
- 從上面可以知道中介軟體佇列的資料結構,所以步驟以就是解析出來 middleware 中介軟體類和引數。
- 如果是陣列結構直接解析出物件
- 最重要的就是第三步了,利用反射來執行中介軟體類,注意在這個執行過程中遞迴呼叫了,而且記住
resolve
返回的始終是閉包,然後在中介軟體之間傳遞,你可以觀察中介軟體的handle
方法的引數,Request
物件,第二個閉包,第三個可選引數,正好對上了這個陣列引數介面,所以handle
方法的$next()
就是在消費佇列。
所以簡單來說,中介軟體的過程就是利用佇列在執行消費。保證了中介軟體的順序執行。
文章轉載於 thinkphp6原始碼分析之中介軟體分析
本作品採用《CC 協議》,轉載必須註明作者和本文連結