前言
在 ThinkPHP 6.0 中,中间件是一个非常重要的功能,特别是在多应用模式下,中间件的执行机制变得更加复杂。本文将详细介绍 ThinkPHP 6.0 + 多应用模式下的中间件触发机制,特别是中间件管道的执行原理。
一、中间件的分类
在 ThinkPHP 6.0 + 多应用模式中,中间件主要分为三类:
- 全局中间件 - 在
app/middleware.php中定义,所有请求都会经过 - 应用中间件 - 在
app/admin/middleware.php中定义,仅在访问 admin 应用时生效 - 路由/控制器中间件 - 在路由或控制器中定义,针对特定路由生效
二、服务发现与注册机制
2.1 Composer 自动发现
在项目安装依赖时,Composer 会执行 post-autoload-dump 脚本:
perl
{
"scripts": {
"post-autoload-dump": [
"@php think service:discover",
"@php think vendor:publish"
]
}
}
2.2 服务发现命令
think service:discover 命令会扫描 vendor/composer/installed.json,查找所有包的 extra.think.services 配置,例如 think-multi-app 的配置:
json
{
"extra": {
"think": {
"services": [
"think\app\Service"
]
}
}
}
2.3 生成服务清单
命令执行后会生成 vendor/services.php 文件:
php
<?php
// This file is automatically generated at:2025-12-25 11:44:34
declare (strict_types = 1);
return array (
0 => 'think\app\Service',
1 => 'think\trace\Service',
);
2.4 服务注册流程
- 应用启动 → 执行初始化器
- 服务注册 →
RegisterService读取vendor/services.php并注册所有服务 - 服务启动 →
BootService调用所有服务的boot()方法
三、MultiApp 中间件的注册
3.1 事件监听器注册
think\app\Service::boot() 方法中注册了 HttpRun 事件监听器:
csharp
public function boot()
{
$this->app->event->listen('HttpRun', function () {
$this->app->middleware->add(MultiApp::class);
});
// ...
}
3.2 事件触发机制
在 vendor/topthink/framework/src/think/Http.php 的 runWithRequest() 方法中:
kotlin
protected function runWithRequest(Request $request)
{
// 加载全局中间件
$this->loadMiddleware();
// 触发 HttpRun 事件
$this->app->event->trigger(HttpRun::class);
return $this->app->middleware->pipeline()
->send($request)
->then(function ($request) {
return $this->dispatchToRoute($request);
});
}
当 HttpRun 事件被触发时,之前注册的监听器会被执行,将 MultiApp 中间件添加到中间件队列中。
四、中间件管道的详细解析
4.1 中间件队列结构
在 Middleware 类中,中间件以队列形式存储:
dart
protected $queue = [
'global' => [/* 全局中间件 */],
'app' => [/* 应用中间件 */],
'route' => [/* 路由中间件 */],
'controller' => [/* 控制器中间件 */]
];
4.2 管道执行机制详解
pipeline() 方法是中间件执行的核心,让我们详细分析:
php
public function pipeline(string $type = 'global')
{
return (new Pipeline())
->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]];
}
$response = call_user_func($call, $request, $next, ...$params);
if (!$response instanceof Response) {
throw new LogicException('The middleware must return Response instance');
}
return $response;
};
}, $this->sortMiddleware($this->queue[$type] ?? [])))
->whenException([$this, 'handleException']);
}
这段代码可以分解为以下几个步骤:
步骤1: 排序中间件
kotlin
$this->sortMiddleware($this->queue[$type] ?? [])
首先获取指定类型的所有中间件,并根据配置的优先级进行排序。
步骤2: 包装中间件为闭包函数
php
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]];
}
$response = call_user_func($call, $request, $next, ...$params);
if (!$response instanceof Response) {
throw new LogicException('The middleware must return Response instance');
}
return $response;
};
}, $this->sortMiddleware($this->queue[$type] ?? []))
这里使用 array_map 将每个中间件包装成一个闭包函数。每个闭包函数都接收两个参数:
$request: 当前请求对象$next: 下一个中间件的执行函数
闭包函数内部的执行逻辑:
- 解析中间件信息,获取调用方法和参数
- 如果是类方法形式,使用容器实例化中间件类
- 执行中间件的
handle方法 - 验证返回值必须是 Response 实例
步骤3: 创建管道并设置中间件
scss
(new Pipeline())
->through( /* 上面生成的闭包数组 */ )
->whenException([$this, 'handleException']);
创建 Pipeline 实例,并设置中间件列表和异常处理器。
4.3 Pipeline 类的执行原理
Pipeline 类使用了函数式编程的 array_reduce 模式来构建中间件执行链:
perl
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes), // 反转中间件数组
$this->carry(), // 传递函数
function ($passable) use ($destination) {
return $destination($passable); // 最终执行目标
}
);
return $pipeline($this->passable); // 执行整个管道
}
carry() 方法的实现:
php
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
try {
return $pipe($passable, $stack); // 执行当前中间件,传入下一个中间件作为 $next
} catch (Throwable | Exception $e) {
return $this->handleException($passable, $e);
}
};
};
}
4.4 执行链的构建过程
假设有三个中间件 A、B、C,执行链的构建过程如下:
-
反转数组: [C, B, A]
-
从右到左构建执行链:
- 初始值:
final_handler(最终执行函数) - 处理 A:
A(request, final_handler) - 处理 B:
B(request, A(request, final_handler)) - 处理 C:
C(request, B(request, A(request, final_handler)))
- 初始值:
这样就形成了一个洋葱模型的执行链,请求从外到内经过每个中间件,再从内到外返回响应。
五、MultiApp 中间件的特殊处理
MultiApp 中间件的 handle 方法会检查是否为多应用请求,如果是,则会执行应用级中间件:
perl
public function handle($request, Closure $next)
{
if (!$this->parseMultiApp()) {
return $next($request);
}
return $this->app->middleware->pipeline('app') // 执行应用中间件
->send($request)
->then(function ($request) use ($next) {
return $next($request); // 继续执行后续中间件
});
}
六、应用级中间件的自动加载
在 MultiApp::loadApp() 方法中,会自动加载应用目录下的中间件配置:
php
if (is_file($appPath . 'middleware.php')) {
$this->app->middleware->import(include $appPath . 'middleware.php', 'app');
}
这解释了为什么在 app/admin/middleware.php 中定义的中间件会被自动加载。
七、完整执行流程图
markdown
请求到达
↓
触发 HttpRun 事件
↓
MultiApp 中间件被添加到队列
↓
执行全局中间件管道
↓
MultiApp 检测是否为多应用
↓
如果是多应用 → 执行应用级中间件管道
↓
执行路由/控制器中间件
↓
执行最终控制器方法
↓
返回响应
八、实际示例
假设我们有以下目录结构:
perl
app/
├── middleware.php # 全局中间件
├── admin/
│ └── middleware.php # admin应用中间件
└── index/
└── middleware.php # index应用中间件
当访问 http://yourdomain.com/admin/index 时,执行顺序为:
- 全局中间件 →
app/middleware.php - MultiApp 中间件 → 检测到是 admin 应用
- 应用中间件 →
app/admin/middleware.php(通过$this->app->middleware->pipeline('app')) - 路由中间件 → 路由定义的中间件
- 控制器中间件 → 控制器中的中间件
- 控制器方法 →
app/admin/controller/Index.php
总结
ThinkPHP 6.0 的多应用中间件机制通过事件系统、服务注册和管道模式实现了灵活的中间件执行流程。整个机制的核心在于:
- 服务发现 → 通过 Composer 自动发现和注册服务
- 事件驱动 → 通过
HttpRun事件动态添加MultiApp中间件 - 管道模式 → 通过 Pipeline 模式串联所有中间件,使用函数式编程的
array_reduce模式构建洋葱模型执行链 - 应用识别 →
MultiApp中间件负责识别应用并加载应用级中间件