## 中間件
從請求過程中可以看出,第一步就是加載的中間件。那么如何加載的呢?看下面這段代碼
```
$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()` 就是在消費隊列。
所以簡單來說,中間件的過程就是利用隊列在執行消費。保證了中間件的順序執行。