中间件兜底需在响应出口前统一拦截:新建uniformjsonresponse中间件,检查非json响应并提取/包装为标准结构,用json()->header()替换;注册时置于responsetrace之后;同时判断php_sapi==='cli'跳过header。

中间件怎么兜底所有响应出口
ThinkPHP 8.0 的 return ['code'=>0,'data'=>[]] 和 json($data) 走的是不同路径,异常、验证失败、未捕获错误默认返回 HTML 或原始数组,结构不一致。中间件必须在响应发出前统一拦截,不能只靠重写 Json 类。
新建 app/middleware/UniformJsonResponse.php,handle() 中做三件事:
- 检查
$response instanceof \think\Response且不是\think\response\Json实例 - 若响应体是数组(如
ValidateException默认输出),用$response->getContent()提取后解析为 PHP 数组,再按标准结构包装:['code' => $arr['code'] ?? 500, 'message' => $arr['message'] ?? 'server error', 'data' => $arr['data'] ?? null] - 若非数组(如 HTML 响应或空内容),强制包装为
['code' => 500, 'message' => 'server error', 'data' => null],然后用json($wrapped)->header('Content-Type', 'application/json; charset=utf-8')替换原响应
注册到 app/middleware.php 时,确保它排在 \think\middleware\ResponseTrace::class 之后、输出之前 —— 写在数组末尾通常安全,但若用了自定义调试中间件,需手动确认执行顺序。
为什么重写 Json 类只改 output() 方法
ThinkPHP 8.0 的 think\response\Json 构造时会根据 json($data, 201, [], ['code'=>1]) 第四个参数自动设置 $this->code 和 $this->message。直接重写 __construct 或破坏初始化逻辑,会导致 return json($data, 400) 失效,HTTP 状态码错乱。
只需在 app\common\response\Json.php 中继承原类,重写 output($data):
protected function output($data): string
{
$wrapped = [
'code' => $this->code,
'message' => $this->message,
'data' => $data,
'timestamp' => time(),
];
return json_encode($wrapped, JSON_UNESCAPED_UNICODE);
}
在 AppServiceProvider::register() 中绑定:$this->app->bind('think\response\Json', \app\common\response\Json::class);。别动其他方法,否则 json($data, 201) 传的 HTTP 状态码会被忽略。
CLI 环境下 header 报错怎么绕过
中间件里调用 header() 在命令行运行时直接报错:Cannot modify header information。ThinkPHP 不会自动跳过,必须显式判断。
在中间件 handle() 开头加判断:
if (PHP_SAPI === 'cli') {
return $next($request);
}
不要用 !isset($_SERVER['HTTP_HOST']) 这类模糊判断 —— CLI 下 $_SERVER 可能被部分框架补全,不可靠;PHP_SAPI 是唯一确定方式。同时注意:单元测试也走 CLI,该逻辑同样生效,避免测试环境意外中断。
分页对象直接 json() 为什么前端拿不到 total
$list = User::paginate(10) 返回的是 think\Paginator 对象,不是数组。return $list 或 json($list) 会触发自动 JSON 序列化,但默认只序列化 items 部分,丢失 total、last_page、has_next_page 等元信息。
必须手动转换:
- 用
$list->toArray()获取完整结构(含data、total、per_page等) - 再套进统一结构:
return ['code'=>0, 'message'=>'ok', 'data'=>$list->toArray()]—— 注意这里不能用json(),否则又进重写的output(),导致嵌套两层data - 如果已启用中间件兜底,可直接
return $list->toArray(),中间件会自动包一层标准结构
别信文档里“框架自动处理分页 JSON”的说法 —— ThinkPHP 8.0 默认不展开分页对象,必须显式调用 toArray() 或 items() + 手动拼 meta。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











