onafterwrite不是thinkphp原生事件,应改用afterwrite(v6.0+)或afterinsert/afterupdate(v5.1/v6.0),并在beforewrite中保存origin数据用于变更对比,避免日志写入失败。

ThinkPHP 模型事件 onAfterWrite 为什么没触发
因为 onAfterWrite 不是 ThinkPHP 原生支持的模型事件名——它根本不存在。官方文档里没有这个钩子,框架源码里也搜不到。你写的代码大概率一直没执行过,日志自然不会记录。
真正可用的是 afterWrite(v6.0+)或 afterUpdate/afterInsert(v5.1/v6.0 兼容写法),它们在数据写入数据库后触发,且明确区分操作类型。
-
afterInsert:仅在save()新增数据时触发(isUpdate为false) -
afterUpdate:仅在save()更新已有数据时触发(isUpdate为true) -
afterWrite:v6.0+ 统一事件,在新增和更新后都触发,但需手动判断$model->isUpdate()
如何在 afterWrite 里安全拿到变更前后的数据
v6.0 的 afterWrite 回调只传入当前模型实例,原始旧数据默认不保留。如果要对比字段变化(比如记录“用户名从 A 改成 B”),必须提前把旧值存下来。
- 在
beforeWrite或beforeUpdate钩子里用$this->origin获取原始数据,并暂存到模型属性,例如:$this->oldData = $this->origin; - 不要依赖
$this->data直接减$this->origin,因为data是当前待写入值,可能含未提交的脏数据 - 避免在
afterWrite中再查一次数据库取旧值——性能差,还可能被并发修改干扰 - 若用 v5.1,
afterUpdate回调第二个参数是旧数据数组,可直接用,但注意它不含关联字段和自动处理字段(如时间戳)
日志写入失败的三个高频原因
即使事件触发了,日志也可能静默丢弃,常见于以下场景:
- 在事件回调里抛出异常但没捕获,导致事务回滚连带日志写入失效(尤其用了
Db::transaction()) - 日志表用的不是同个数据库连接,而主库挂了或配置了读写分离,写日志走的是只读从库 —— 报错
SQLSTATE[HY000]: General error: 1290 The MySQL server is running with the --read-only option - 日志模型本身也绑定了事件,形成递归调用(比如日志模型的
afterWrite又触发新日志),最终爆栈或超时
建议日志写入改用 think\facade\Log 写文件,或用独立连接的 Db::connect('log')->table('sys_log')->insert([...]),绕过模型事件链。
v5.1 和 v6.0 的事件注册方式差异很大
写法不对,事件压根不会绑定上:
- v5.1:在模型类里定义
protected $eventAfterWrite = 'afterWrite';,再加一个同名方法public function afterWrite() { ... } - v6.0:统一用
protected $dispatchesEvents = ['afterWrite' => AfterWriteEvent::class];,然后单独写事件监听器类;或者更简单——直接在模型中重写afterWrite方法(无需注册) - 别在控制器里用
Model::event('afterWrite', ...)全局注册,它对单次请求有效,且容易被后续请求覆盖
跨版本迁移时,最稳妥的是放弃 onAfterWrite 这种臆造名,统一改用 afterWrite + 版本适配判断。
真正难的不是写日志,是确保它在事务失败时不误记、在高并发时不漏记、在字段被中间件/自动转换后仍能准确比对。别让日志本身成为系统不稳定的一个隐性源头。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










