[TOC]
## 1 Middleware實現
### 0 實現文件
~~~
\library\think\Middleware.php
~~~
### 1 添加middleware
#### 1 add()
~~~
public function add($middleware)
{
if (is_null($middleware)) {
return;
}
$middleware = $this->buildMiddleware($middleware);
if ($middleware) {
$this->queue[] = $middleware;
}
}
~~~
>[danger] add()方法將中間件$middleware添加到中間件隊列queue數組的末尾
>
>其中的buildMiddleware()方法會根據傳入的$middleware不同形式進行不同處理。
>$middleware主要分為 閉包中間件 和 類中間件
>
>buildMiddleware()在下面的輔助方法一節詳細分析
#### 2 unshift()
~~~
public function unshift($middleware)
{
if (is_null($middleware)) {
return;
}
$middleware = $this->buildMiddleware($middleware);
if ($middleware) {
array_unshift($this->queue, $middleware);
}
}
~~~
>[danger] unshift()方法將$middleware中間件添加到中間隊列queue數組的開始部分
>
>與add()方法一樣要經過buildMiddleware()方法處理
>
>不同的是add()添加到中間件隊列末尾,unshift()添加到中間件隊列開始
>
#### 3 import()
~~~
public function import(array $middlewares = [])
{
foreach ($middlewares as $middleware) {
$this->add($middleware);
}
}
~~~
>[danger] import()添加多個中間件到中間件隊列queue的末尾
>
### 2 調用middleware
#### 1 dispatch()
~~~
public function dispatch(Request $request)
{
return call_user_func($this->resolve(), $request);
}
~~~
>[danger] disptach()依次調用中間件,
>其中的resolve()方法實現中間件執行的具體過程
>resolve()在下面的輔助方法一節詳細分析
#### 2 all()
~~~
public function all()
{
return $this->queue;
}
~~~
>[danger] all()獲取已注冊的中間件隊列
>
## 2 Middleware調用
>[danger] 中間件在框架整體執行流程的入口在App.php的run()方法中
~~~
$this->middleware->add(function (Request $request, $next) use ($dispatch, $data) {
if (is_null($data)) {
try {
// 執行調度
$data = $dispatch->run();
} catch (HttpResponseException $exception) {
$data = $exception->getResponse();
}
}
// 輸出數據到客戶端
if ($data instanceof Response) {
$response = $data;
} elseif (!is_null($data)) {
// 默認自動識別響應輸出類型
$isAjax = $request->isAjax();
$type = $isAjax ? $this->config('app.default_ajax_return') : $this->config('app.default_return_type');
$response = Response::create($data, $type);
} else {
$response = Response::create();
}
return $response;
});
$response = $this->middleware->dispatch($this->request);
~~~
>[danger] 在App.php的run()方法的最后
> 獲取app容器的middleware屬性,也就是全局的中間件對象。
> 調用middleware對象的add()方法
> 添加應用調度`$data = $dispatch->run();`到中間件隊列末尾
> 接著調用middleware對象的disptach()開始依次執行中間件
>[danger]在框架的初始化文件base.php(thinkphp\base.php)中注冊middleware類到容器中
~~~
// 注冊核心類到容器
Container::getInstance()->bind([
'app' => App::class,
'build' => Build::class,
'cache' => Cache::class,
'config' => Config::class,
'cookie' => Cookie::class,
'debug' => Debug::class,
'env' => Env::class,
'hook' => Hook::class,
'lang' => Lang::class,
'log' => Log::class,
'middleware' => Middleware::class,
'request' => Request::class,
'response' => Response::class,
'route' => Route::class,
'session' => Session::class,
'url' => Url::class,
'validate' => Validate::class,
'view' => View::class,
'rule_name' => route\RuleName::class,
// 接口依賴注入
'think\LoggerInterface' => Log::class,
]);
~~~
>[danger]因此可以在App中通過$this->middleware獲取Middleware對象
## 3 輔助方法
#### 1 buildMiddleware()
~~~
protected function buildMiddleware($middleware)
{
if (is_array($middleware)) {
list($middleware, $param) = $middleware;
}
if ($middleware instanceof \Closure) {
return [$middleware, isset($param) ? $param : null];
}
if (!is_string($middleware)) {
throw new InvalidArgumentException('The middleware is invalid');
}
if (false === strpos($middleware, '\\')) {
$value = Container::get('config')->get('middleware.' . $middleware);
$middleware = $value ?: Container::get('app')->getNamespace() . '\\http\\middleware\\' . $middleware;
}
if (is_array($middleware)) {
return $this->import($middleware);
}
if (strpos($middleware, ':')) {
list($middleware, $param) = explode(':', $middleware, 2);
}
return [[Container::get($middleware), 'handle'], isset($param) ? $param : null];
}
~~~
>[danger] buildMiddleware()方法主要根據middleware是閉包還是類進行不同的處理
>
>
>**1** 首先假設是單個中間件注冊,單個中間可以是帶參數的閉包和類。
> 首先檢查是否為數組,然后將其分為真正的中間件$midlleware和$param。
> 如果$middleware是閉包 那么直接返回`[$middleware,$param]`形式的數組,然后添加到中間件隊列中。
>
> 如果不是閉包,則檢查是否為類名字符串,
> 如果類名字符串中包含命名空間`\\`則
> 首先讀取配置文件middleware.php注冊的中間信息。
> 如果從配置文件中middleware.php中獲取失敗,則從應用目錄app\http\middleware中讀取對應的中間件類。
>
>
>**2** 然后假設傳入的數組中間件,則調用impore()進行批量注冊 。
> 如果傳入的是一個數組則調用import()方法注冊多個中間件。
> import()方法遍歷數組信息再次調用buldMiddleware()處理多個中間件。
> 類的中間件數組格式是`[[[Container::get($middleware), 'handle'], isset($param) ? $param : null];]`也就是以中間件類的handle()方法為調用方法,傳入參數$param,
>
> **3** 綜上buildeMiddleware()方法處理多個中間件和單個中間件的參數。其中單個中間件還分為閉包中間件和類中間件
>
#### 2 resolve
~~~
protected function resolve()
{
return function (Request $request) {
$middleware = array_shift($this->queue);
if (null === $middleware) {
throw new InvalidArgumentException('The queue was exhausted, with no response returned');
}
list($call, $param) = $middleware;
try {
$response = call_user_func_array($call, [$request, $this->resolve(), $param]);
} catch (HttpResponseException $exception) {
$response = $exception->getResponse();
}
if (!$response instanceof Response) {
throw new LogicException('The middleware must return Response instance');
}
return $response;
};
}
~~~
>[danger] reolve()方法則依次將注冊到中間件隊列的中間件組織為一個**洋蔥形式的遞歸調用閉包**
>
>首先array_shift()獲取中間件隊列的第一個中間件
>list()將注冊的中間件分為中間件調用和中間件參數
>然后call_user_func_array()調用中間件
>需要注意的是call_user_func_array()的參數,
>第一個是中間件調用$call
>第二個是$this->resolve()方法,
>第三個參數才是真正傳入中間件的參數$param.
>這里$this->resolve()將會再次獲取下個中間件的閉包形式,
>下個中間件的閉包中將再次獲取下下個中間件的閉包形式。
>因此這里resolve()將中間件組織成為一個遞歸調用閉包。
>
>**然后執行第一層的閉包,進入第二層閉包,然后再執行第三層閉包,直到最后一層,然后再往上依次返回第一層閉包。**
>
>這里是中間件執行順序的核心邏輯