thinkphp中间件采用洋葱模型,执行依赖正确签名($request, $next)、$next被调用且返回response实例;through()需传闭包数组,终点then()也必须返回response。

ThinkPHP 的中间件执行不是线性顺序,而是洋葱模型——请求进去一圈,响应出来一圈。关键不在“有多少层”,而在每一层的 $next($request) 是否被正确调用、返回值是否为 Response 实例、中间件签名是否匹配。错一个,链就断,要么 500,要么静默跳过。
中间件函数签名必须是 function($request, $next)
TP8 的 Pipeline 在运行时会自动向每个中间件闭包传入两个参数:当前 $request 和下一个执行闭包 $next。它不关心你类名或方法名,只认这个签名。
- ✅ 正确写法(闭包):
function($request, $next) { return $next($request); } - ✅ 正确写法(类方法):确保
handle()方法声明为public function handle($request, Closure $next),且不要多加第三个参数(除非你显式传了中间件参数,如['AuthMiddleware', 'admin']) - ❌ 错误写法:
public function handle(Request $request)—— 缺$next,报Too few arguments - ❌ 错误写法:
public function handle($request, $next, $role = 'user')——Pipeline只传两个,第三个参数没值,同样报错 - 调试技巧:在中间件开头加
var_dump(gettype($next), $next instanceof Closure);,确认$next真的是闭包
through() 传的是闭包数组,不是类名数组
很多人以为 through([AuthMiddleware::class, CheckRoleMiddleware::class]) 就能跑,其实 TP8 的 Pipeline::through() 接收的是「已封装好的中间件执行单元」,即每个元素都必须是 function($request, $next) 形式的闭包。
- 框架内部用
array_map把类名转成闭包,你手动传就必须自己做这步,否则$next不会被注入 - 手动构造示例:
$pipes = [function($req, $next) { return (new AuthMiddleware())->handle($req, $next); }]; $pipeline->through($pipes); - 别直接传
[AuthMiddleware::class],那只是字符串,Pipeline不会自动实例化或反射调用 - 如果用了依赖注入(比如中间件构造函数要
CacheInterface),确保容器已绑定,否则make()失败,闭包构建失败,through()后实际传入的是空数组
中断流程必须返回 Response 实例,不能只 return
洋葱模型允许提前终止,但“终止”不是靠 return 跳出函数,而是靠「返回一个非闭包类型的值」——TP8 强制要求中间件最终返回 Response 对象,否则抛出 LogicException: The middleware must return Response instance。
- ✅ 权限拒绝示例:
if (!$user->can('delete')) { return response()->json(['error' => 'Forbidden'], 403); } - ✅ 登录检查跳转:
if (!$request->session()->has('user_id')) { return redirect('/login'); } - ❌ 错误写法:
if (!$auth) { echo 'no access'; exit; }—— 没返回Response,且破坏了管道契约 - ❌ 错误写法:
if (!$auth) { return; }—— 返回null,触发异常 - 注意:一旦返回
Response,后续所有$next($request)都不会执行,包括控制器;但已进入的中间件后置逻辑(如果有)也不会自动触发,洋葱的“回程”只发生在正常流转时
send()->then() 的终点必须是可调用且返回 Response
send($request)->then(...) 的 then() 是洋葱最内层的“果肉”,也就是控制器执行点。它不是中间件,但受同一套契约约束。
- ✅ 正确终点:
->then(function($request) { return $controller->exec(); }),前提是$controller->exec()返回Response - ❌ 终点返回数组:
->then(function($request) { return ['data' => 1]; })—— 不是Response,最终响应体为空或报错 - ⚠️ 常见陷阱:控制器方法里用了
echo或var_dump,导致输出提前刷出,后续Responseheader 发送失败 - 调试建议:在
then()闭包末尾加var_dump($response instanceof Response);,确认终点确实交出了合规响应
真正难的不是写中间件,而是理解 $next 是一个闭包而不是一个方法名,以及 Response 是唯一合法出口。漏掉任何一个环节,洋葱就塌成一摊泥——不是卡死,就是静默失效,还很难定位。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











