模型修改器仅在模型的save()、create()、update()等方法中触发,Db::insert()或原生SQL插入时不生效;setFieldAttr签名须为public function setFieldAttr($value, $data = null),且$type转换优先于修改器执行。
模型修改器只在模型的 save()、create()、update() 等方法中触发,直接调用 Db::insert() 或原生 SQL 插入时完全不生效。
这是最常被忽略的前提。TP6 的修改器(setFieldAttr)是模型层的逻辑,不是数据库驱动或查询构建器的功能。
UserModel::create(['email' => '[email protected]'])、$user->name = 'x'; $user->save();
Db::name('user')->insert(['email' => '[email protected]'])、$model->data($data)->isUpdate(false)->save();(绕过模型校验与修改器流程)->insert()(如 $model->insert($data))也不会触发修改器——因为那是底层 Query 操作,跳过了模型的 save() 生命周期setFieldAttr 函数签名与参数陷阱修改器函数必须严格匹配签名 public function setFieldAttr($value, $data = null),否则框架无法识别或调用失败。
$value 是你传入的原始值(比如 '[email protected]'),不是数据库字段当前值$data 是当前要写入的完整数据数组(仅在 save() 时存在;create() 中可能为空)function setEmailAttr($value)),导致 PHP 7+ 严格模式下报 Declaration must be compatible 警告,修改器静默失效public function setEmailAttr($value, $data = null){ return strtolower($value);}
$type)与修改器的执行顺序冲突如果模型同时定义了 $type 和 setFieldAttr,TP6 会先做类型转换,再进修改器——这意味着你拿到的 $value 可能已是转换后的类型,而非原始字符串。
protected $type = ['price' => 'integer'];+
public function setPriceAttr($value) { return $value * 100; } → 传入 '19.99' 会被先转成整型 19,再进修改器变成 1900,而非预期的 1999
$type,改由修改器统一处理(推荐);要么在修改器里手动还原原始输入(需配合 getData() 或前置缓存)dump($value); die;,确认它是不是你认为的“原始值”replace(true) 和修改器毫无关系有用户误以为 replace(true) 是开启修改器的开关,其实它只影响 SQL 类型(REPLACE INTO vs INSERT INTO),和模型逻辑完全解耦。
$model->replace(true)->save($data) 会触发修改器(因为仍是 save())$model->replace(true)->insert($data) 不会触发修改器(因为走的是底层 insert())think/db/Mysql::insert() 中,$replace 参数只用于拼 SQL 字符串,不参与模型事件分发真正决定修改器是否运行的,是「是否经过模型的 save() 流程」,而不是某个布尔配置项或 SQL 关键字。只要绕开模型写入入口,哪怕写满 10 个 setXxxAttr,也只会安静地躺在那里,一动不动。