Yii2 RESTful接口统一返回格式应通过response组件的on beforeSend事件或自定义ApiJsonFormatter实现,而非在控制器中直接return数组或手动json_encode,以确保状态码、Content-Type和序列化逻辑正确。

Yii2 RESTful 接口要统一返回格式,核心不是改每个 action,而是接管 Response 的序列化流程。直接在控制器里 return ['code' => 0, 'data' => ...] 看似简单,但会绕过状态码、Content-Type、异常响应一致性等关键环节,最终导致前端收不到正确 status code 或解析失败。
为什么继承 yii\rest\Controller 后返回数组还是没统一结构?
因为 Yii2 默认只做「格式协商」和「基础序列化」,不自动注入 code、message 这类业务字段。它的默认行为是:状态码 → HTTP status,数据 → 原样 JSON 化,中间不加壳。
- 检查是否启用了
ContentNegotiator:它在yii\rest\Controller::behaviors()中默认存在,负责根据Accept头选FORMAT_JSON,但不会改数据结构 - 不要手动设
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON—— 这会跳过ContentNegotiator,且后续无法被统一拦截 - 确认你的请求带了
Accept: application/json,否则可能 fallback 到 HTML 格式(尤其用浏览器直接访问时)
用 Response::on beforeSend 统一封装最稳
这是官方推荐、侵入最小、兼容所有控制器(包括 ActiveController 和自定义 RestController)的方式。它在响应发出前最后一步修改 $response->data,不影响状态码、Header、序列化逻辑。
- 在
config/web.php的response组件中配置:
'response' => [
'class' => 'yii\web\Response',
'on beforeSend' => function ($event) {
$response = $event->sender;
// 只处理 JSON 响应,且 data 是数组(排除文件下载、重定向等)
if ($response->format === \yii\web\Response::FORMAT_JSON && is_array($response->data)) {
$code = $response->statusCode;
$response->data = [
'code' => $code < 400 ? 0 : $code,
'message' => \Yii::$app->response->statusText,
'data' => $code >= 400 ? null : $response->data,
];
}
},
],
- 注意:异常响应(如
throw new NotFoundHttpException())也会走这里,所以无需额外捕获异常 - 如果模块有特殊逻辑(如某个子模块不需要封装),可在闭包内加判断,比如
if (Yii::$app->controller->module->id === 'legacy') return;
自定义 ApiJsonFormatter 适合需要深度控制序列化的场景
当你要区分成功/失败的字段结构(比如失败时强制返回 error 而非 data)、或需对接第三方协议(如微信支付回调要求固定 key 名),就该用自定义 ResponseFormatterInterface。
- 它比
on beforeSend更底层,能完全接管 JSON 编码过程,支持对$response->data做任意变形 - 必须注册到
response.formatters中,并把format设为FORMAT_JSON才生效 - 常见坑:
format()方法里不能调$response->setData(),而要直接赋值$response->data = ...;否则会递归触发 formatter - 示例关键逻辑:
public function format($response)
{
$data = $response->data;
if (is_array($data) && isset($data['code'])) {
return; // 已封装,不重复处理
}
$response->data = [
'code' => $this->mapStatusCode($response->getStatusCode()),
'message' => $this->getMessageByCode($response->getStatusCode()),
'data' => $response->getStatusCode() >= 400 ? null : $data,
];
$response->setContentType('application/json');
}
别在控制器里手动 json_encode() 或 echo
这是最容易踩的坑:看似“快”,实则破坏整个响应生命周期。
-
echo json_encode(...)会让Response对象丢失状态码、Header、缓存策略,前端看到的永远是 200 OK -
Yii::$app->response->content = json_encode(...)会跳过formatters,导致中文乱码、JSON 转义失效等问题 - 唯一合法的“绕过”方式是:设置
format = \yii\web\Response::FORMAT_RAW并return字符串,但仅限导出、Webhook 回调等极少数场景
真正需要定制结构的接口,应该用 on beforeSend 或自定义 Formatter —— 它们确保 status code、Content-Type、charset 全部正确,只是多包了一层 JSON。


















