不 return $next($request) 会导致请求静默中断,因框架要求 handle() 必须返回 think\response 实例,否则返回 null 或非 response 值将使响应链断裂,造成浏览器无响应、504 超时且无错误日志。

不 return $next($request) 会导致请求静默中断
ThinkPHP 的中间件执行链是严格同步的,handle() 方法必须返回一个 think\Response 实例。如果漏掉 return,或只写 $next($request)(没 return),PHP 默认返回 null;返回 null、string、array 或其他非 Response 对象,框架就无法继续向下派发响应,整个请求会卡住——浏览器无响应、日志无错误、控制台也不报错,只看到超时或空页面。
- 常见现象:接口一直 loading,Nginx 日志显示 504,但 TP 日志里没记录控制器执行
- 调试技巧:在
handle()末尾加一行throw new \Exception('unreachable');,如果这行没抛出,说明前面已经提前退出了 - 注意:哪怕你只是想“记录日志后放行”,也必须显式写
return $next($request),不能省略return
为什么不能用 echo / die / exit 替代 return
ThinkPHP 的响应生命周期由框架统一接管,所有输出必须走 think\Response 实例。直接 echo、die() 或 exit() 会绕过响应头设置、事件触发、钩子回调等关键流程,导致:
- CORS 头、缓存头、Content-Type 等全部丢失
- 后续中间件(如日志、性能统计)完全不执行
- 异常处理机制失效,错误无法被
ExceptionHandler捕获 - 单元测试中无法 mock 响应行为
正确拦截方式只有两种:return json(['code' => 401]) 或 return abort(403) —— 它们最终都生成标准 Response 实例。
return $next($request) 是洋葱模型的执行支点
$next($request) 不是“调用下一个函数”那么简单,它是整个中间件栈的递归入口。它的调用会同步阻塞当前中间件,直到控制器返回响应,再逐层回退执行 $next($request) 后面的代码(即“洋葱内层”)。这意味着:
- 你在
$next($request)前写的代码,是“请求进入”阶段(如鉴权、日志开头) - 你在
$next($request)后写的代码,是“响应返回”阶段(如耗时统计、Header 注入) - 如果没
return,后面代码根本不会执行,洋葱就断在半路
比如跨域中间件里,if ($request->isOptions()) { return response('', 204); } 必须早于 return $next($request),否则 OPTIONS 请求会误入路由匹配,大概率触发 405。
注册位置错误会让 handle() 根本不运行
即使 handle() 写得完全正确,如果中间件没被加载,它就等于不存在。关键检查点:
-
app/middleware.php文件必须存在,且必须返回数组(不能是null、echo、var_dump) - 数组元素要是完整类名字符串,例如
\app\middleware\Cors::class或'app\middleware\Cors',不能写成Cors::class(缺少命名空间) - 路由级注册要用
->middleware(Cors::class),别漏掉use或写错类名大小写 - 开启
'middleware_trace' => true后看响应头X-Middleware-Stack,如果列表里没有你的中间件,说明它压根没进执行链
最常被忽略的是:app/middleware.php 被删了、改名了、或者里面写了 echo 'test'; 导致返回值不是数组——这种情况下,整个中间件系统形同虚设,连 handle() 的第一行都不会执行。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











