
在 laravel 中,当子模型(如 comment)更新时,可通过 `touches` 属性自动更新直接父模型(如 post)的 `updated_at`;若需进一步更新“祖父级”模型(如 user),需结合模型事件手动触发 `touch()`,本文详解实现方法与最佳实践。
在 Laravel 的 Eloquent ORM 中,$touches 属性仅支持单层向上触达——即子模型更新时自动调用其直接关联父模型的 touch() 方法。例如,Comment 模型设置 protected $touches = ['post'],可在评论更新时自动刷新对应 Post 的 updated_at;但该机制不会递归生效,因此在 Post 模型中声明 protected $touches = ['user'] 并不能使 User 时间戳随 Comment 更新而变更。
要实现跨两级的级联时间戳更新(如 Comment → Post → User),推荐采用 Eloquent 模型事件 + 显式 touch() 调用 的组合方案。核心思路是:在中间模型(Post)监听自身 updated 事件,并主动触达其关联的 User 模型。
以下为具体实现步骤:
✅ 正确做法:在 Post.php 中使用 booted() 注册事件监听
belongsTo(User::class);
}
/**
* 模型启动时注册事件监听器
*/
protected static function booted()
{
static::updated(function (self $post) {
// 确保关联关系存在且未软删除,避免异常
if ($post->user && ! $post->user->trashed()) {
$post->user->touch();
}
});
}
}? 说明:static::updated() 是静态闭包监听器,在每次 Post 实例被成功更新后执行;$post->user->touch() 会更新该用户的 updated_at(仅限 updated_at 字段,默认不修改 created_at)。
⚠️ 注意事项与健壮性增强
- 空值与软删除防护:务必检查 $post->user 是否存在,以及是否已被软删除(trashed()),否则可能抛出 ModelNotFoundException 或静默失败。
- 性能考量:touch() 会发起一次额外的 UPDATE 查询。若业务对性能极度敏感,可考虑使用数据库触发器或队列异步更新,但通常此开销可接受。
- 事务一致性:该事件在数据库事务内同步执行,能保证 Post 和 User 时间戳更新的原子性(前提是 User::touch() 不被重写为非事务安全逻辑)。
- 避免循环触发:确保 User 模型未监听自身 updated 事件并反向触达 Post,否则将引发无限递归更新。
✅ 验证效果(示例流程)
// 创建用户、文章、评论 $user = User::create(['name' => 'Alice']); $post = $user->posts()->create(['title' => 'Hello']); $comment = $post->comments()->create(['body' => 'First comment']); // 初始时间戳 dump($user->updated_at); // 2024-01-01 10:00:00 dump($post->updated_at); // 2024-01-01 10:00:05 // 更新评论 → 触发 Post::touches → 更新 Post.updated_at $comment->update(['body' => 'Updated comment']); // 此时 Post.updated_at 变更,同时因 Post::updated 事件触发,User.updated_at 也同步更新 dump($user->fresh()->updated_at); // 2024-01-01 10:00:10 ← 已更新!
? 总结
- touches 是轻量级单级联动工具,适合 Child → Parent;
- 多级联动(Child → Parent → Grandparent)必须借助模型事件显式控制;
- 推荐统一在中间模型(如 Post)中处理上层触达逻辑,职责清晰、易于维护;
- 始终添加关联存在性校验,保障生产环境稳定性。
通过上述方式,你即可精准、可靠地实现 Laravel 中任意深度的父模型时间戳级联更新。










