Yii2中需在config/web.php的response组件中配置formatters数组,键名须与Accept头主类型(如'json')精确匹配,值为类名或配置数组;自定义类必须重写formatJson()和formatJsonp()方法,并注意$data已是处理后的PHP结构。

Yii2 中如何注册自定义 ResponseFormatter
直接在应用配置里挂载,别试图在控制器里临时改——ResponseFormatter 是全局响应管道的环节,运行时替换会失效或被覆盖。
常见错误是写完类就去 Controller::beforeAction() 里调用 $response->formatters['json'] = new MyJsonFormatter,这完全没用,因为格式化器在 Response::send() 前就被固定使用了。
- 在
config/web.php的'components' => ['response' => [...]]里配'formatters'数组 - 键名必须和
Accept头匹配(如'json'对应application/json),不能随便起名 - 值必须是类名字符串或配置数组,不支持匿名函数或对象实例(框架会自己 new)
'response' => [
'class' => 'yii\web\Response',
'formatters' => [
'json' => [
'class' => 'app\components\MyJsonFormatter',
'prettyPrint' => YII_DEBUG,
],
],
],
自定义 JsonResponseFormatter 必须重写的两个方法
不是继承就完事,formatJson() 和 formatJsonp() 都得显式覆盖——框架不会自动代理,漏掉任一个,对应请求类型就会 fallback 到默认行为,比如 callback=xxx 请求直接 500。
典型场景:统一加 code/message 包裹、过滤敏感字段、处理 DateTime 序列化。别在 formatJson() 里做耗时操作(如查库),它在响应发送前才执行,会影响所有 JSON 接口。
-
formatJson()处理普通application/json -
formatJsonp()处理带callback参数的 JSONP(即使你不用,也得空实现,否则报Call to undefined method) - 父类的
$encodeOptions默认不含JSON_UNESCAPED_UNICODE,中文会转义,记得补上
public function formatJson($response)
{
$data = $response->data;
$response->data = [
'code' => isset($data['code']) ? $data['code'] : 0,
'message' => isset($data['message']) ? $data['message'] : 'OK',
'data' => $data['data'] ?? $data,
];
parent::formatJson($response);
}
为什么 $response->data 在 formatter 里还是原始数组?
因为 Yii 的 formatter 只负责“编码”,不负责“构造数据结构”。你在 Controller 里写的 return ['user' => $model] 或 $this->asJson([...]),到 formatter 时已经是 PHP 数组/对象,没经过任何中间层加工。
容易踩的坑:有人想在 formatter 里自动把 ActiveRecord 转成数组,结果发现 $response->data 已经是 toArray() 后的结果——那是 JsonResponseFormatter 自己干的,你再转一次会出错;如果传进来的是对象且没实现 JsonSerializable,PHP 会直接报 TypeError。
- 检查控制器返回值类型:避免直接 return 模型对象,显式调用
$model->toArray() - 若需统一序列化逻辑,应该在基类控制器或行为(Behavior)里拦截
asJson(),而不是塞到 formatter - formatter 里对
$response->data做json_encode()是重复劳动,框架已做
调试 formatter 不生效的三个关键检查点
90% 的“自定义不生效”问题出在这三处,别急着翻源码。
- 确认请求头
Accept: application/json存在且没被 nginx / Apache 改写(比如反向代理清掉了头) - 检查
Response::$formatters数组里键名是否和Accept值精确匹配(application/json; charset=UTF-8仍走'json',但application/vnd.api+json就不会) - 断点打在
Response::send()里,看$this->formatters[$this->getContentType()]拿到的是不是你的类——如果不是,说明匹配失败或配置没加载
复杂点在于 Content-Type 解析依赖 yii\web\Response::getContentType() 的内部映射表,它不读 MIME 类型全量字符串,只取主类型(application/json 的 json 部分)。别指望靠改 Accept 头来绕过这个机制。


















