🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[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()将中间件组织成为一个递归调用闭包。 > >**然后执行第一层的闭包,进入第二层闭包,然后再执行第三层闭包,直到最后一层,然后再往上依次返回第一层闭包。** > >这里是中间件执行顺序的核心逻辑