最稳妥方式是用json()封装统一结构,核心字段为code、msg、data、timestamp,推荐Trait混入控制器并集中管理错误码,需严防输出流污染。

直接用 json() 封装统一结构最稳妥,关键不是“怎么写”,而是“怎么避免破坏输出流”和“怎么让结构可维护”。TP6 本身不强制要求统一格式,但项目一旦变大,没规范的接口返回会让前端反复适配、日志难排查、错误难定位。
核心结构设计:code + msg + data + timestamp
这是前后端共识度最高的基础字段组合:
- code:业务状态码(非 HTTP 状态码),比如 200 成功、400 参数错误、500 服务异常
- msg:简短提示语,面向前端或调试人员,不建议放敏感信息
- data:实际业务数据,成功时为对象/数组,失败时可为空或含补充信息
- timestamp:便于排查时序问题,也方便前端做本地缓存策略
封装方式:Trait 比基类更灵活
推荐用 Trait(如 JsonResponse)混入控制器,避免继承链过深。示例结构:
- 定义一个
app\common\response\JsonResponseTrait - 提供
jsonSuccessData($data)、jsonData($code, $msg, $data)等快捷方法 - 所有方法最终都调用私有
jsonResponse(),确保结构唯一出口 - 错误码集中管理在
ApiErrCode类中,用常量或枚举,避免硬编码
避坑重点:输出干净比逻辑漂亮更重要
TP6 的 json() 对前置输出零容忍,常见翻车点:
- PHP 文件保存为 UTF-8 无 BOM(尤其 config/、route/、中间件文件)
- 控制器方法开头加
if (headers_sent()) die('headers already sent');快速定位污染源 - 禁用
echo/var_dump,调试改用Log::debug()或 IDE 断点 - 自定义异常类的
render()方法必须返回json(),不能拼字符串或调response()->content()
进阶建议:配合中间件自动注入公共字段
如果需要全局加 version、request_id 或签名字段,不要每个接口手动塞,可用中间件:
- 在响应发送前拦截,用
$response->withHeader()加 HTTP 头 - 若需注入到 JSON body,可在 Trait 的
jsonResponse()中动态合并 - 注意:中间件里不能调
json(),只能操作已生成的 Response 实例

















