[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()将中间件组织成为一个递归调用闭包。
>
>**然后执行第一层的闭包,进入第二层闭包,然后再执行第三层闭包,直到最后一层,然后再往上依次返回第一层闭包。**
>
>这里是中间件执行顺序的核心逻辑