修改器必须严格按set字段名Attr命名(如setPasswordAttr),仅在save()等模型写入操作中触发,不支持批量更新、禁止修改其他字段或调用save(),返回值须为标量。

修改器(setter)怎么写才生效
修改器必须严格按命名规范定义,否则模型完全不会调用。方法名格式是 set字段名Attr,字段名首字母大写,且不能带下划线(如数据库字段是 user_name,对应方法名是 setUserNameAttr)。
常见错误:写成 setUser_nameAttr、setusernameAttr 或漏掉 Attr 后缀——这些都不会触发。
- 密码加密要放在
setPasswordAttr里,不是setPasswdAttr - 时间字段自动转时间戳,用
setUpdateTimeAttr,传参可为空(内部自动取当前时间) - 如果字段名含下划线,建议在模型中用
$schema显式声明,并统一用驼峰访问,避免歧义
修改器里能直接改其他字段吗
不能。修改器只负责当前字段的写入前处理,它接收的是单个字段的原始值,返回处理后的值。想联动改别的字段(比如改 status 时自动填 updated_by),得用模型事件或重写 save() 方法。
强行在 setStatusAttr 里给 $this->updated_by = 123 是无效的——该赋值不会被写入,也不会触发其他字段的修改器。
立即学习“PHP免费学习笔记(深入)”;
- 需要字段联动更新,优先用
saving事件:protected static function boot(): void { self::saving(function ($model) { $model->updated_by = Auth::id(); }); } -
setXXXAttr内部禁止调用$this->save()或任何查询/更新操作,会引发递归或事务异常 - 修改器返回值必须是标量或 null;返回数组、对象会报错或静默丢弃
批量更新时修改器还起作用吗
不起作用。所有基于 SQL 的批量更新(User::update()、Db::table()->update()、where()->update())都绕过模型实例,不加载数据、不触发获取器/修改器、不走验证和事件。
只有通过模型实例调用的 save()(包括 saveAll())才会逐条触发修改器。
- 想让批量更新也走修改器逻辑,只能先
select()查出数据,遍历调用save()——但性能差、易锁表,仅适合小数据量 - 如果字段需加密或格式化,又必须批量执行,建议把逻辑提到业务层,手动处理后再传给
update() -
allowField()和force()对修改器无影响,它们只控制字段白名单和是否跳过变更检测
修改器和自动时间戳冲突怎么办
当模型已开启 $autoWriteTimestamp = true,又在 setUpdateTimeAttr 里手动返回时间,会导致重复写入或格式不一致。ThinkPHP 会优先采用修改器返回值,覆盖自动时间戳逻辑。
所以要么关掉自动时间戳($autoWriteTimestamp = false),全靠修改器控制;要么干脆别写 setUpdateTimeAttr,让框架自动处理。
- 若需自定义时间格式(如毫秒级时间戳),用
setType(['update_time' => 'integer'])配合setUpdateTimeAttr返回microtime(true) - 若字段类型是 datetime,修改器返回字符串必须符合
'Y-m-d H:i:s'格式,否则入库为 0000-00-00 00:00:00 - 软删除字段(如
delete_time)的修改器极少需要自定义,除非你要控制「软删时额外记录操作人」这类特殊逻辑
trace() 或 Log::debug() 到修改器内部,确认它是否真的被调用了。



















