thinkphp模型字段修改日志需通过模型事件(如after_update)手动实现,因全局事件无模型上下文、无法获取新旧数据差异;须在模型内注册事件并用getorigin()/getdata()比对,推荐数据库异步记录。

ThinkPHP 本身不自动记录模型字段修改日志,必须手动介入——最可靠的方式是用 模型事件(也叫钩子函数),而不是 HTTP 层事件或普通事件类。
为什么不用 AppInit / HttpEnd 这类全局事件
这类事件在请求生命周期中触发,无法精准捕获「哪条数据被改了」「改了哪些字段」。比如用户编辑个人资料,你只关心 User 模型的 update 行为,而不是整个请求结束时干点什么。
- 全局事件没有模型上下文,拿不到原始数据和更新后数据对比
- 无法区分是批量更新还是单条更新,容易误记日志
- 若控制器没走模型(比如直接 DB::table()->update()),事件根本不会触发
模型事件怎么注册才生效
模型事件必须在模型类里定义,并通过 protected static $event 或 init() 注册,否则不触发。
- 在模型类顶部声明:
protected static $event = ['after_update' => 'onAfterUpdate']; - 或在
init()方法里调用:self::event('after_update', [self::class, 'onAfterUpdate']); - 方法名不能随意改,必须匹配官方支持的钩子名:
before_insert、after_update、before_delete等 - 注意:只有调用
save()、update()等模型方法才会触发;原生 SQL 不触发
如何拿到修改前后的字段差异
after_update 钩子接收的参数是当前模型实例,它自带 getOrigin() 和 getData(),可直接比对。
public static function onAfterUpdate($model)
{
$origin = $model->getOrigin(); // 修改前数据
$current = $model->getData(); // 修改后数据
$diff = array_diff_assoc($current, $origin);
if (!empty($diff)) {
\think\facade\Log::write('ModelUpdate', sprintf(
'ID:%d, Fields:%s',
$model->id,
json_encode($diff)
));
}
}
- 别用
$model->toArray()替代getData(),前者可能包含冗余字段或格式化后值 -
getOrigin()只在模型从数据库查出后修改才有效;新建后立刻 save 不会触发after_update - 日志内容建议存数据库而非文件,避免高并发写冲突;可用
Db::name('log')->insert()异步写入
订阅多个模型变更时别硬编码监听
如果要统一处理 User、Order、Article 的更新日志,不要在每个模型里重复写 onAfterUpdate,改用事件订阅类 + 动态绑定。
- 生成订阅类:
php think make:subscribe ModelUpdateLog - 在
app/event.php中注册:'subscribe' => ['app\subscribe\ModelUpdateLog'], - 订阅类里监听多个事件:
public function subscribe(Event $event)返回数组:['User.after_update', 'Order.after_update'] - 关键点:订阅类方法接收的是完整事件名字符串(如
User.after_update),需从中提取模型名和操作类型再分发
最容易被忽略的是:模型事件默认不传递事件名参数,onAfterUpdate 方法只收到模型实例;若需知道是哪个模型触发的,得靠反射或命名规范来反推,别指望框架自动塞进去。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











