根本原因是字段未被标记为safe,导致load()阶段被过滤,模型中无新值;需在rules()中为对应场景配置safe或校验规则,并确保JSON请求时正确解析数据并调用load()。

为什么$model->save()没更新某些字段
根本原因不是数据库或SQL出错,而是这些字段没被标记为 safe,导致 load() 阶段就被过滤掉了——save() 操作时模型里压根没有新值。
常见错误现象:表单提交了 email 和 status,但只有 email 入库,status 始终是旧值;调试发现 $model->status 在 load() 后仍是 null 或原始值。
-
load()是批量赋值入口,它只把rules()中声明为safe的字段写入模型属性 - 没出现在任何规则里的字段(哪怕写了
required但漏了on场景匹配),默认unsafe - 即使字段在数据库中可写、在视图里可见,只要没进模型,
save()就无法更新它
rules()里加safe规则不等于允许更新
safe 规则本身不校验,只开“赋值通道”;但它必须和其他规则配合才能生效,否则形同虚设。
典型误用:['status', 'safe'] 单独一行,但没配场景,也没其他规则触发它。
- 单独写
['status', 'safe']只在default场景下起作用;若控制器里设了$model->scenario = 'update',这行规则就完全不生效 - 更稳妥的写法是绑定校验逻辑:
['status', 'in', 'range' => [0, 1, 2], 'on' => 'update']—— 这条规则自动让status在update场景下变成safe - 如果真需要无校验的赋值(比如后台强制修改),可用
['status', 'safe', 'on' => 'admin-update'],再在控制器中显式切换场景
update()方法绕过safe限制的风险
有人用 $model->update(false, ['status']) 直接更新字段,看似绕过了 safe 限制,实则埋下权限和一致性隐患。
这种写法跳过所有验证、事件、行为钩子,连 beforeSave() 都不会触发。
- 字段值未经任何规则检查(如范围、格式、关联约束),可能写入非法数据
- 不会触发
ActiveRecord::EVENT_BEFORE_UPDATE,审计日志、缓存清理等逻辑全部丢失 - 若该字段本应受 RBAC 控制(比如只有管理员能改
is_verified),这里就彻底失效 - 正确替代方案:定义专用场景 + 显式
load()+validate()+save(),把权限控制收口到模型层
JSON请求下safe字段突然失效
API 接口用 application/json 提交时,load() 默认找不到数据源,返回 false,但很多人不检查返回值,直接 validate(),结果字段始终没进模型。
错误示例:$model->load(\Yii::$app->request->getBodyParams()) —— 对 JSON 请求无效,因为 getBodyParams() 不解析原始 body。
- 必须先手动解码:
$data = \yii\helpers\Json::decode(\Yii::$app->request->getRawBody()) - 再调用
$model->load($data, ''),第二个参数传空字符串,否则默认按formName()匹配键名(如user[status]),而 JSON 通常是扁平结构{"status": 1} - 此时
safe规则仍有效,但要注意:若用load($data, ''),所有字段都可能被赋值,务必确认rules()已严格限定白名单
真正难的不是写 safe,而是确保每次请求都经过 load() → scenario 切换 → validate() 这个闭环。少一环,字段就“消失”在半路,而且毫无提示。


















