前置中间件在控制器执行前拦截请求,用于登录校验、权限控制等;后置中间件在控制器执行后处理响应,用于日志记录、响应头注入等,二者由$next()调用位置决定。

ThinkPHP中间件的“前置”和“后置”不是靠注册方式区分的,而是由 handle() 方法里 $next() 的调用位置决定的——它在逻辑前还是逻辑后,直接决定中间件是拦在控制器前面,还是跟在响应后面。
前置中间件:$next($request) 放在最后
典型场景是登录校验、参数过滤、请求限流。这类中间件必须在控制器执行前介入,否则权限或数据已经失控。
- 写法上,所有判断和跳转逻辑必须放在
return $next($request);之前 - 常见错误:把
redirect()写在$next()后面,结果控制器先执行了才重定向,用户看到的是空白页或混合响应 -
$request是唯一可用参数,此时控制器名、方法名都还没解析出来,request()->controller()会返回空或默认值 - 示例中若漏掉
return,比如只写$next($request);,PHP 返回null,框架视为无响应,浏览器卡住或报 500
后置中间件:$response = $next($request) 放在最前
适合日志记录、响应头注入、JSON 格式统一包装、异常兜底处理。它依赖控制器已执行完毕并返回 Response 对象。
- 必须先调用
$next($request)获取$response,再做后续操作,否则没东西可处理 - 此时可以安全使用
request()->controller()和request()->action(),因为路由分发已完成 - 注意:如果控制器抛出未捕获异常,
$next($request)可能不返回Response,而是一个Exception,需用try/catch包裹或配合app_end钩子兜底 - 修改响应体时,避免直接拼接字符串;推荐用
$response->withContent()或$response->header()
POST 数据在中间件里读不到?顺序和缓存是关键
不是中间件写错了,而是 ThinkPHP 的 $request->post() 底层调用的是 fgets(STDIN) 或 php://input,只能读一次。一旦被前面的中间件(包括框架内置的 ValidatePostSize)提前消费,后面就为空。
- 优先用
$request->param():它内部做了缓存,多次调用不会丢数据,且自动合并 GET/POST/ROUTE 参数 - 如果必须读原始 body(如 JSON 接口),确保它是第一个执行的中间件,并用
$request->getContent()读取后,通过$request->withAttribute('raw_body', $body)存起来供后续使用 - 别在多个中间件里反复调
$request->post()或$request->input(),这是静默失败的高发点 - 调试时可用
var_dump($request->getInput())看原始输入流是否已被清空
中间件注册后不执行?先查这三个地方
写完类、加了 handle()、也 return 了 $next(),但断点就是不进——大概率卡在注册环节。
- 检查
app/middleware.php返回的数组里是不是写了完整命名空间,比如'app\middleware\AuthCheck',而不是'AuthCheck'或'app\http\middleware\AuthCheck'(路径要和实际文件位置一致) - 确认类文件名严格匹配类名:类是
AuthCheck,文件必须叫AuthCheck.php,大小写敏感,尤其在 Linux 环境下 - 路由级中间件要配别名:比如
Route::get('admin', 'Admin/index')->middleware('auth'),前提是已在config/middleware.php中配置了'auth' => \app\middleware\AuthCheck::class - 控制器级中间件只对当前控制器生效,且不能用在闭包路由里(
Route::get(..., function(){})不走控制器生命周期)
真正容易被忽略的,是中间件链的洋葱模型本身——每一层都必须显式 return 响应对象,少一层 return,整个链就断在那一环,连错误日志都不一定留下。这不是语法问题,是 ThinkPHP 请求生命周期的硬约束。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











