必须用Response::setSendCallback()在send()前拦截重写,仅处理Content-Type为application/json的响应,并确保中间件优先级高于JsonResponse。

中间件里怎么统一拦截并转换响应为 JSON
ThinkPHP 的中间件在 app/middleware.php 或路由/控制器绑定中生效,但关键点在于:不能在中间件里直接修改已输出的响应体。真正可行的方式是拦截 Response 对象,在它被发送前重写内容和 Header。
常见错误是试图在中间件里用 echo json_encode(...) 或直接 return 新 Response,结果导致重复输出或 header 已发送报错(Cannot modify header information)。
- 必须使用
$response->withContent()+->header()重建响应对象 - 推荐在中间件
handle()方法末尾返回修改后的$response,而不是提前 return - 若控制器已返回 JSON 字符串(如
return json([...])),该中间件会二次编码——需先判断原始响应类型
如何识别并跳过已为 JSON 的响应
ThinkPHP 原生 json() 函数返回的是 think\Response 实例,其 getHeader('content-type') 通常含 application/json;而普通数组或字符串响应则没有。靠这个判断可避免套娃:
if (strpos($response->getHeader('content-type')[0] ?? '', 'application/json') !== false) {
return $response;
}
更稳妥的做法是检查响应内容是否已为合法 JSON 字符串(用 json_decode($response->getContent(), true) 并判断是否非 null 且无 JSON_ERROR_SYNTAX)。
立即学习“PHP免费学习笔记(深入)”;
- 不要只看控制器 return 类型——有些逻辑可能 return 数组,有些 return
view(),中间件需一视同仁 - 若项目混用 HTML 和 API 路由,建议通过路由分组或中间件参数控制生效范围,比如只对
api/前缀启用 - 注意 CLI 环境下
getHeader()可能返回空数组,需加空值判断
怎么兼容异常和错误时的 JSON 输出
默认异常页面是 HTML,但 API 场景下需要统一返回 JSON 错误结构。这不能单靠中间件解决——异常处理流程在中间件之前就可能终止执行。
正确做法是配合 app/exception.php 中的 render() 方法,对 HttpException 和自定义业务异常统一转 JSON:
if (request()->isAjax() || request()->header('accept') === 'application/json') {
return json(['code' => $e->getCode() ?: 500, 'msg' => $e->getMessage()]);
}
中间件只管“正常流程”的响应,异常流必须另走异常处理器,两者缺一不可。
- 别指望中间件捕获所有
throw new Exception——未被捕获的异常会直接触发render() -
request()->isAjax()不可靠,建议优先检查Acceptheader 或约定请求带X-Requested-With: XMLHttpRequest - 404、405 等 HTTP 异常默认不进中间件 handle,必须在
render()里显式处理
实际部署时 Content-Type 和状态码容易漏设
很多人只改了 body,忘了设 Content-Type: application/json; charset=utf-8 和正确的 HTTP 状态码,导致前端解析失败或 axios 默认 reject 2xx 以外的状态。
完整重写响应应包含三要素:
$data = ['code' => 0, 'msg' => 'ok', 'data' => $response->getContent()];
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
return $response->withContent($json)
->header('Content-Type', 'application/json; charset=utf-8')
->code(200);
-
JSON_UNESCAPED_UNICODE避免中文变 \uXXXX - 如果原始响应是 400 错误,中间件里仍设 200 就会掩盖问题——建议透传原状态码:
->code($response->getCode()) - Apache 下注意
AddType application/json .json这类配置不会影响 PHP 输出,无需额外处理



















