概述
thinkphp6中,中间件是针对http组件来说的,在http组件入库方法runWithRequest里
// 加载全局中间件
$this->loadMiddleware();
// 执行全局中间件
return $this->app->middleware->pipeline()// pipeline方法里将中间件转为闭包
// 初始化
->send($request)
// 执行
->then(function ($request) {
// 最后执行路由分发
return $this->dispatchToRoute($request);
});
// 加载基础目录下的中间件
protected function loadMiddleware(): void
{
if (is_file($this->app->getBasePath() . 'middleware.php')) {
$this->app->middleware->import(include $this->app->getBasePath() . 'middleware.php');
}
}
核心实现逻辑
通过中间件组件来实现,队列queue里有全局类型的中间件队列、route类型的队列、controller类型的队列
class Middleware
{
// 队列
protected $queue = [];
/** *******添加中间件******** */
public function import(array $middlewares = [], string $type = 'global'): void
{
foreach ($middlewares as $middleware) {
$this->add($middleware, $type);
}
}
public function add($middleware, string $type = 'global'): void
{
$middleware = $this->buildMiddleware($middleware, $type);
if (!empty($middleware))
// 队列里添加中间件
$this->queue[$type][] = $middleware;
// 队列去重
$this->queue[$type] = array_unique($this->queue[$type], SORT_REGULAR);
}
}
// 中间件可以是数组、闭包、字符串
protected function buildMiddleware($middleware, string $type): array
{
if (is_array($middleware)) {
[$middleware, $params] = $middleware;
}
if ($middleware instanceof Closure) {
// 闭包+参数
return [$middleware, $params ?? []];
}
if (!is_string($middleware)) {
throw new InvalidArgumentException('The middleware is invalid');
}
//中间件别名检查
$alias = $this->app->config->get('middleware.alias', []);
if (isset($alias[$middleware])) {
$middleware = $alias[$middleware];
}
if (is_array($middleware)) {
$this->import($middleware, $type);
return [];
}
// 类名+handle方法+参数,如果没有传入方法名称,默认是handle方法
return [[$middleware, 'handle'], $params ?? []];
}
public function route($middleware): void
{
// route类型的队列
$this->add($middleware, 'route');
}
public function controller($middleware): void
{
// controller类型的队列
$this->add($middleware, 'controller');
}
/** *******管道执行中间件******** */
public function pipeline(string $type = 'global')
{
return (new Pipeline())
// through是把中间转为闭包,方便后续执行
->through(array_map(function ($middleware) {
// 返回中间件闭包数组,也就把中间件转为闭包
return function ($request, $next) use ($middleware) {
// 调用中间件
[$call, $params] = $middleware;
if (is_array($call) && is_string($call[0])) {
$call = [$this->app->make($call[0]), $call[1]];
}
// 必须有request、next参数、外加自己传入的参数
// next参数是下一个要执行的中间件闭包,所以在回调函数里必须被调用,不然后续无法执行
$response = call_user_func($call, $request, $next, ...$params);
// 返回http响应类,因为最后一个执行的是dispatchToRoute
if (!$response instanceof Response) {
throw new LogicException('The middleware must return Response instance');
}
return $response;
};
}, $this->sortMiddleware($this->queue[$type] ?? [])))// 排序中间件,并且传给array_map,用于把中间件转为闭包
// 设置如果有异常的时候,处理异常的方法名称
->whenException([$this, 'handleException']);
}
public function handleException($passable, Throwable $e)
{
// 系统异常类
$handler = $this->app->make(Handle::class);
$handler->report($e);
return $handler->render($passable, $e);
}
}
管道类
class Pipeline
{
// 闭包形式的中间件存在这里
protected $pipes = [];
// 这种闭包管道
public function through($pipes)
{
$this->pipes = is_array($pipes) ? $pipes : func_get_args();
return $this;
}
// 执行管道
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);
}
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
try {
return $pipe($passable, $stack);
} catch (Throwable | Exception $e) {
return $this->handleException($passable, $e);
}
};
};
}
}
管道的核心逻辑
简化下上述的管道逻辑,如下所示执行闭包:
$middleware = [
function ($request1, $next) {
echo 'request1闭包';
// $next为request2闭包
return $next($request1);
},
function ($request2, $next) {
echo 'request2闭包';
// $next为request3闭包
return $next($request2);
},
function ($request3, $next) {
echo 'request3闭包';
// $next为last闭包
return $next($request3);
}
];
// 需要倒叙,因为闭包执行是按相反的顺序进行的
$middleware = array_reverse($middleware);
$pipe = array_reduce($middleware, function ($stack, $pipe) {
// $stack永远指向上一个闭包
return function ($passable) use ($stack, $pipe) {
// 第一次执行:$stack=last闭包
// 第二次执行:$stack=request1闭包
// 第三次执行:$stack=request2闭包,此时$pipe为request3闭包
return $pipe($passable, $stack);
};
}, function ($requestLast) {
echo 'last闭包';
});
$pipe('request');