必须在echo json_encode()前设置header('Content-Type: application/json; charset=utf-8'),且echo后立即exit;否则响应体易被空格、BOM、调试输出污染,导致前端JSON.parse()报Unexpected token <等错误。

为什么直接 echo json_encode() 会导致前端反复报错
因为没控制输出时机和响应头,前端拿到的常是 text/html 类型或混入了调试信息。常见现象包括:fetch 解析失败、Unexpected token < in JSON、Vue 的 data 字段突然变成字符串而非对象。根本原因不是 JSON 写得不对,而是 PHP 输出流被污染——比如在 echo json_encode() 前已有空格、BOM 或 var_dump(),或者之后还有代码执行。
- 必须在输出前调用
header('Content-Type: application/json; charset=utf-8') -
echo后必须接exit或die,不能只return - 避免在框架视图层、模板文件里拼 JSON,统一收口到逻辑层
- CLI 环境下禁止调用
header(),需提前判断PHP_SAPI !== 'cli'
用适配器模式封装 response() 函数的关键设计点
适配器不是加一层壳,而是把「业务数据组装」和「HTTP 响应输出」彻底解耦。你不需要改模型或数据库查询逻辑,只需让所有控制器最后调用同一个出口函数。
- 函数签名建议为
response($data, $message = '', $code = 0, $http_status = 200),其中$code是业务码(如1001),$http_status是真实 HTTP 状态码(如401) - 不自动映射状态码文案,
$message必须由调用方传入,避免硬编码导致多语言或定制化失效 - 对
$data做类型预检:若为资源(resource)、mysqli_result或未实现JsonSerializable的对象,抛出明确异常,例如Cannot JSON encode resource #7 - 不处理敏感字段脱敏——那是 Controller 层该干的事,比如
unset($user['password']),response() 只负责干净地输出
如何让 error 响应也走同一套流程
很多人给 success 写了封装,却在 catch 里手写 echo json_encode(['code'=>500]),结果格式不一致、状态码错位、甚至漏设 header。正确做法是:所有分支都走 response(),只是参数不同。
- 正常流程:
response($user, '获取成功', 0, 200) - 参数错误:
response([], '用户名不能为空', 4001, 400) - 服务异常:
response([], '数据库连接失败', 5001, 500) - 不要在
catch里调http_response_code(500)后再 echo,直接交给response()统一发
这样前端只要约定一套解析逻辑,就能处理全部接口,不用为每个错误路径单独写 if 分支。
立即学习“PHP免费学习笔记(深入)”;
为什么别急着引入 crell/api-problem 或 phpro/api-problem
这些包确实符合 RFC 9457,但它们解决的是「标准错误详情」,不是「统一响应结构」。如果你的项目连 data 字段有时有有时没有、message 在成功时是空字符串在失败时是数组、code 一会儿是布尔一会儿是整数——那先别碰 RFC,先把基础结构钉死。
- crell/api-problem 默认不兼容你现有的
{code, message, data}格式,要额外做字段映射 - 它强制要求
type和title字段,而你的前端可能只认code - 生产环境若已上线几十个接口,改响应结构比加一个包风险高得多
- 真正需要它的场景是:要对接外部系统、需提供机器可读的错误分类、或已有 OpenAPI 文档强约束
先用轻量级 response() 把格式焊牢,等字段稳定、前端适配完成,再考虑升级错误体规范。



















