ThinkPHP模型save前自动补全字段推荐用$insert/$update属性配合setXxxAttr修改器,或before_write事件;避免data()赋值绕过自动填充,注意时间类型匹配。

模型 save() 前如何自动补全字段(如 create_time、update_time、status)
ThinkPHP 的模型默认不会自动填充字段,除非你显式配置了 autoWriteTimestamp 或定义了 type 类型字段。直接赋值 create_time 或靠数据库默认值,在业务逻辑中容易遗漏或被覆盖。
推荐用模型的 protected $auto、$insert、$update 属性控制字段自动填充时机:
-
$auto:新增和更新都触发(慎用,比如update_time不该在 insert 时填) -
$insert:仅插入时生效,适合create_time、created_by、status等初始值 -
$update:仅更新时生效,适合update_time、updated_by
示例:
class User extends Model
{
protected $insert = ['create_time', 'status'];
protected $update = ['update_time'];
protected function setCreateTimeAttr()
{
return date('Y-m-d H:i:s');
}
protected function setUpdateTimeAttr()
{
return date('Y-m-d H:i:s');
}
protected function setStatusAttr()
{
return 1; // 默认启用
}
}
用 setXxxAttr() 方法动态推导字段值(如根据 name 生成 slug)
当字段需要基于其他字段计算得出(比如 slug 来自 name),不能只靠静态值,得用访问器 + 修改器组合。关键点是:修改器(setXxxAttr)会在 save() 前被调用,且接收当前数据数组,可读取其他字段。
立即学习“PHP免费学习笔记(深入)”;
注意:不要在修改器里调用 $this->name —— 此时模型属性尚未完全赋值,应从参数中取:
- 修改器函数签名是
function setSlugAttr($value, $data),$data是完整待写入数据 - 如果
$value已传入(比如手动设置了slug),就别覆盖;否则才推导 - 避免无限递归:推导逻辑里别再触发其他 setXxxAttr
示例:
protected function setSlugAttr($value, $data)
{
if (!empty($value)) {
return $value;
}
if (isset($data['name'])) {
return \think\facade\Str::slug($data['name']);
}
return '';
}
使用事件(event)在 save 前做更复杂的字段补全(如关联用户信息)
当补全逻辑涉及查询数据库(比如根据 dept_id 查出 dept_name)、调用服务类、或需事务一致性时,模型属性和修改器不够用,得上事件机制。
ThinkPHP 6+ 推荐在模型类中定义 protected $eventAfterFind 等钩子,但字段补全主要用 before_write(对应 save 和 update):
- 在模型中注册:
protected $eventBeforeWrite = ['beforeWrite']; - 定义方法:
public function beforeWrite(),在里面修改$this->data - 注意:此时
$this->data是即将写入的数据,直接改数组元素即可,例如$this->data['updated_by'] = \think\facade\Session::get('user_id'); - 若需查库,建议用
Db::name()而非 new 模型,避免嵌套事件触发
常见踩坑:$append / $hidden 和自动填充字段的关系
$append 是用于序列化输出的额外字段,跟写入无关;$hidden 只影响 toArray() 或 JSON 输出。它们对 save() 过程零影响 —— 别指望靠 $append 触发字段生成。
另一个高频错误:在控制器里先 $model->data([...]) 再 save(),结果 $insert 规则失效。因为 data() 会绕过模型的自动处理流程。正确做法是用 allowField() + 直接赋值,或统一走 save(['name' => 'xxx']) 数组方式。
还有就是时间字段类型不匹配:数据库是 datetime,但代码返回的是时间戳(int),会导致写入 0000-00-00。务必保持类型一致 —— date('Y-m-d H:i:s') 或用 time() 配合数据库字段类型为 int。



















