ThinkPHP模型自动写入创建/更新时间需在模型中声明$createTime和$updateTime并开启自动写入,否则不生效;字段名须与数据库一致(如下划线),类型推荐DATETIME/TIMESTAMP;$readonly = ['create_time']防止更新时被覆盖;状态变更时间需在beforeWrite钩子中比对$origin与$data动态写入独立字段(如disable_time),禁用复用同一字段记录多状态时间。

ThinkPHP 模型如何自动写入创建/更新时间
直接用 createTime 和 updateTime 字段配合模型配置就能搞定,不用手写 save() 前的赋值逻辑。关键在模型类里声明这两个字段,并开启自动写入——否则哪怕字段存在、类型正确,也不会触发。
常见错误是只加了数据库字段,但没在模型中设置:protected $createTime = 'create_time'; 和 protected $updateTime = 'update_time';。更隐蔽的坑是字段名用了下划线而配置写了驼峰(比如写成 'createTime'),导致匹配失败,时间戳始终为 null。
- 字段类型建议用
DATETIME或TIMESTAMP,避免用INT手动转时间戳——ThinkPHP 自动写入默认走date('Y-m-d H:i:s') - 如果用
INT存时间戳,得额外设protected $type = ['create_time' => 'integer'];并重写getCreateTimeAttr(),否则入库值会变成字符串 -
updateTime在调用save()且主键存在时才更新;用where()->update()不触发自动更新
停用/激活状态变更时怎么自动记时间
ThinkPHP 没内置“状态变更时间”字段支持,得靠事件或手动拦截。最稳的方式是在模型的 beforeWrite 钩子里判断状态变化,再动态写入对应字段,比如 disable_time 或 enable_time。
典型场景:用户点击「停用账号」,你改了 status 字段从 1 变 0,同时要记下停用时刻。不能依赖前端传时间,也不能等入库后再查一遍旧值——得在写入前就比对原始数据和当前数据。
- 用
$this->data['status'] !== $this->origin['status']判断是否真变了状态 - 只在变化发生时写时间:
if ($this->data['status'] == 0 && $this->origin['status'] == 1) { $this->data['disable_time'] = date('Y-m-d H:i:s'); } - 注意
$this->origin只在更新操作中可用;新增时为空,需单独处理 - 别在控制器里做这个逻辑——状态含义可能变(比如 2 是冻结、3 是注销),模型层才该知道每个数字代表什么
为什么 createTime 在 update() 时也被改了
这是字段配置漏掉 readonly 导致的。ThinkPHP 的 createTime 默认只在新增时写入,但如果模型没显式声明 protected $readonly = ['create_time'];,当调用 where()->update(['name' => 'xxx']) 这类批量更新时,create_time 会被一并置为当前时间。
根本原因是 ThinkPHP 把所有已定义的自动时间字段都纳入更新字段集,除非你把它放进 $readonly 数组。这不是 bug,是设计如此——它假设你清楚哪些字段绝不该被覆盖。
- 必须加:
protected $readonly = ['create_time'];,即使你只用updateTime也得加 -
readonly对save()和update()都生效,但不影响数据库层面的ON UPDATE CURRENT_TIMESTAMP - 如果用了软删除(
delete_time),也要加进$readonly,否则删完再 update 会清空删除时间
多状态流转时时间字段容易混乱
一个字段记录多个状态的时间(比如用 status_time 表示「最后状态变更时间」)看似省事,实际会让后续查询和审计变得模糊。真正需要区分的是「创建」「启用」「停用」「注销」这些动作各自发生的时间点,它们语义不同、不可互相替代。
最容易被忽略的是时间字段的可空性与业务约束冲突。例如 enable_time 允许为 null,但「启用后才能停用」这种规则没法靠数据库强制,得在模型里用钩子校验:停用前检查 enable_time 是否非空,否则报错。
- 每个有明确生命周期意义的动作,配独立时间字段,命名直白如
enable_time、disable_time、cancel_time - 不要复用同一个字段存多种状态时间,否则查「账号首次启用时间」和「最近一次停用时间」就得靠 case when 或多次 join
- 字段默认值设为
NULL,别用0000-00-00 00:00:00——MySQL 8.0+ 默认 strict mode 下会报错
状态时间不是写进去就完事,关键是每次状态跳转时,模型得知道自己在响应哪个业务动作,而不是只盯着字段值变没变。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










