管道模式执行全局中间件
Http类的run方法
public function run(Request $request = null): Response
{
//自动创建request对象
$request = $request ?? $this->app->make('request', [], true);
$this->app->instance('request', $request);
try {
// 得到 Response 类对象
$response = $this->runWithRequest($request);
...
}
runWithRequest 方法
protected function runWithRequest(Request $request)
{
...
// 执行了路由获得 Response 实例,其间经过全局中间件过滤
return $this->app->middleware->pipeline()
->send($request)
->then(function ($request) {
return $this->dispatchToRoute($request);
});
}
框架中的全局中间件默认都是注释起来的,现在我们打开两个
<?php
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
\think\middleware\LoadLangPack::class,
// Session初始化
\think\middleware\SessionInit::class,
// 页面Trace调试
// \think\middleware\TraceDebug::class,
];
$this->app->middleware->pipeline() 实例化 middleware 类,执行 middleware 的 pipeline 方法
public function pipeline(string $type = 'global')
{
return (new Pipeline())
->through(array_map(function ($middleware) {
return function ($request, $next) use ($middleware) {
[$call, $param] = $middleware;
if (is_array($call) && is_string($call[0])) {
$call = [$this->app->make($call[0]), $call[1]];
}
$response = call_user_func($call, $request, $next, $param);
if (!$response instanceof Response) {
throw new LogicException('The middleware must return Response instance');
}
return $response;
};
}, $this->sortMiddleware($this->queue[$type] ?? [])))
->whenException([$this, 'handleException']);
}
这里就是返回了一个 pipeline 类的实例,同时调用 pipeline 的 through 方法
public function through($pipes)
{
$this->pipes = is_array($pipes) ? $pipes : func_get_args();
return $this;
}
结果是 pipeline 类实例的 pipes 属性保存了一个数组,数组中的每个元素都是一个闭包函数,闭包函数内执行了中间件类的 handle 方法
pipeline 的 then 方法
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes),
$this->carry(),
function ($passable) use ($destination) {
try {
return $destination($passable);
} catch (Throwable | Exception $e) {
return $this->handleException($passable, $e);
}
});
return $pipeline($this->passable);
}
我们把 carry 方法的代码合并过来,去掉异常捕捉的代码,看下
$pipeline = array_reduce(
array_reverse($this->pipes),
function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
return $pipe($passable, $stack);
}
},
function ($passable) use ($destination) {
return $destination($passable);
});
前面我们打开了两个全局中间件,pipes 数组中就有两个闭包, array_reduce 会迭代两次,我们来看下第一次迭代
此时 $stack 的初始值是 array_reduce 的第三个参数,我们命名为 A
(A)
function ($passable) use ($destination) {
return $destination($passable);
};
array_reduce 第二个参数的返回值是 B
(B)
function ($passable) use ($stack, $pipe) {
return $pipe($passable, $stack);
}
将 A 代入 B,得到第一次迭代的返回值 C,也是第二次迭代的 $stack 的值
(C)
function ($passable) use ($stack, $pipe) {
return $pipe($passable, function ($passable) use ($destination) {
return $destination($passable);
});
}
第二次迭代,将 C 代入 B,得到 D
(D)
function ($passable) use ($stack, $pipe) {
return $pipe($passable, function ($passable) use ($stack, $pipe) {
return $pipe($passable, function ($passable) use ($destination) {
return $destination($passable);
});
});
}
最终 $pipeline 就是 D 这样一个超级闭包,这里 use 的两个参数 $stack 已经更换成了相关代码, $pipe 是执行中间件 handle 方法的闭包
或者我们可以这样理解这个闭包 D,当然 handle 方法不是静态的,明白意思就好
function ($passable) {
return LoadLangPack::handle($passable, function ($passable) {
return SessionInit::handle($passable, function ($passable) use ($destination) {
return $destination($passable);
});
});
}
$destination($passable) 方法就是执行路由,返回 response
中间件代码的执行顺序
handle 方法的代码大概是这样的
public function handle($request, Closure $next)
{
// 前置操作
$response = $next($request);
// 后置操作
return $response;
}
我们这里的实际执行顺序就是 LoadLangPack::handle 前置操作 -> SessionInit::handle 前置操作 -> 执行路由 -> SessionInit::handle 后置操作 -> LoadLangPack::handle 后置操作
本作品采用《CC 协议》,转载必须注明作者和本文链接