yii restful接口默认通过serializer组件+fields()/extrafields()链路序列化数据,而非直接调用toarray();fields()控制必显字段及值逻辑,extrafields()支持expand参数按需加载关联。

Yii RESTful 接口默认怎么序列化数据
Yii 的 ActiveController 默认用 ActiveRecord::toArray() 序列化模型,但**不是直接调用 toArray()**,而是走 Serializer 组件 + fields() / extraFields() 链路。这意味着:字段是否出现、值是否被转换、关联是否展开,全由这组方法控制,而非模型本身的属性列表。
常见错误现象:GET /api/users/1 返回了 password_hash、auth_key 等敏感字段;或明明定义了 getFullName(),却没出现在 JSON 里;又或者关联的 profile 数据为空数组,但实际数据库有记录。
-
fields()决定「哪些字段必须存在」,返回键值对(key 是响应字段名,value 是取值逻辑) -
extraFields()决定「哪些字段可选展开」,比如通过?expand=profile触发 - 不重写这两个方法时,Yii 会 fallback 到 ActiveRecord 默认行为:只暴露 public 属性 + getter 方法(且方法名需带
get前缀) - 所有字段值都会经过
Serializer::serialize()处理,比如Date转字符串、ActiveRecord实例转数组
如何用 fields() 过滤敏感字段和计算字段
最常用也最安全的方式是在模型里重写 fields(),而不是在控制器或行为里做后置过滤——因为序列化发生在响应生成早期,早过滤早省资源。
示例:用户模型中屏蔽密码相关字段,并暴露计算字段:
public function fields()
{
$fields = parent::fields();
// 移除敏感字段
unset($fields['password_hash'], $fields['auth_key'], $fields['password_reset_token']);
// 添加计算字段
$fields['full_name'] = function () {
return trim($this->first_name . ' ' . $this->last_name);
};
return $fields;
}
- 返回数组的 key 是最终 JSON 的字段名,value 可以是字符串(对应属性或 getter)、匿名函数(支持任意逻辑)、或 null(表示该字段不输出)
- 不要在
fields()里做 DB 查询或 heavy 计算,它会在每次序列化时执行 - 如果某个字段依赖关联模型(如
$this->profile->avatar_url),确保关联已 eager-loaded,否则会触发 N+1 - 想让某个字段只在特定场景出现?不能靠条件判断动态改
fields()返回值——那会破坏缓存和一致性;应改用extraFields()+expand参数
expand 参数怎么触发关联数据加载
expand 不是魔法开关,它只是告诉 Yii:「把这几个 extraField 名称对应的关联,提前用 with() 加载进来」。前提是:该字段已在模型的 extraFields() 中声明,且对应的是合法关联名或 getter。
示例:允许通过 ?expand=profile,orders 加载用户资料和订单列表:
public function extraFields()
{
return ['profile', 'orders'];
}
// 并确保关联定义正确:
public function getProfile()
{
return $this->hasOne(Profile::class, ['user_id' => 'id']);
}
public function getOrders()
{
return $this->hasMany(Order::class, ['user_id' => 'id']);
}
-
expand参数值必须严格匹配extraFields()返回的键名,大小写敏感 - 多个值用逗号分隔,如
?expand=profile,orders;空格会被忽略,但别加空格以防客户端编码问题 - Yii 会自动调用
ActiveQuery::with(),所以关联查询是一次性完成的,不会 N+1 - 如果关联不存在或未定义 getter,请求不会报错,只是对应字段为
null或空数组 - 注意性能:
orders可能返回几百条,别在不分页的情况下直接expand—— 应配合自定义 search logic 或专用接口
为什么 fields() 有时不生效
最常被忽略的一点:你改了模型的 fields(),但控制器用的是 ActiveDataProvider 或自定义数组返回,压根没走模型序列化流程。
典型场景:
- 在
actionIndex()里手动 newActiveDataProvider,但 query 来自UserSearch::search(),而UserSearch是普通 Model,没继承ActiveRecord,自然没有fields()行为 - 用
asArray()查询,返回纯数组,绕过了所有模型层的fields()和序列化逻辑 - 控制器里写了
return ['data' => $users],其中$users是 AR 对象数组,但没设置Yii::$app->response->format = Response::FORMAT_JSON,导致 Yii 用默认格式(可能是 HTML)输出 - 用了自定义
Serializer类并覆盖了serialize()方法,但没调用父类逻辑,fields()就被跳过了
验证是否走对路径:在模型的 fields() 方法里加个 die('hit'),发请求看是否中断——不中断,说明根本没调用到这个模型的序列化逻辑。











