当业务逻辑与数据操作强耦合但需避免模型膨胀时,应使用模型观察者;它仅响应生命周期事件,不参与业务判断、不依赖请求上下文,且需正确注册、避免事务问题并谨慎调用队列。

什么时候该用模型观察者而不是直接写在模型里
当业务逻辑和数据操作强耦合、但又不想让模型变成“上帝类”时,模型观察者是更干净的选择。比如用户注册后要发邮件、更新积分、同步到第三方系统——这些都不该塞进 User::create() 的回调里。
常见错误现象:在 creating 或 saving 里直接调用外部 API 或事务性操作,结果模型保存失败但邮件已发出;或者把一堆 if-else 判断塞进观察者方法里,导致难以测试和复用。
- 观察者只响应生命周期事件,不参与业务判断逻辑 —— 判断该不该发邮件,应该放在事件监听器或服务类里
- 避免在
updating中修改$model->attributes,Laravel 可能忽略或触发二次保存 - 如果需要访问旧值(比如密码变更才重置 token),用
$model->getOriginal('password'),别依赖$model->password
Laravel 10+ 中注册观察者的两种方式及兼容性差异
新版 Laravel 推荐用服务容器自动发现,但老项目迁移时容易漏掉配置或搞混注册时机。
使用场景:你刚写完一个 UserObserver 类,运行时却完全没触发 —— 很可能是因为没注册,或注册太晚(比如在路由闭包里注册)。
- 自动发现需满足:类名以
Observer结尾,放在App\Observers命名空间,并在AppServiceProvider::boot()中调用Model::observe(UserObserver::class) - 手动注册更可控:
User::observe(UserObserver::class)必须在模型被加载后执行,推荐放AppServiceProvider::boot()末尾 - Laravel 10+ 默认关闭自动发现,必须显式启用:
php artisan event:discover,否则EventServiceProvider里的$listen不生效
模型观察者里调用队列任务的正确姿势
观察者本身是同步执行的,一旦里面写了耗时操作(如发短信、调用 HTTP API),会拖慢整个请求响应。很多人直接在 created 里 dispatch,却忘了处理失败重试或事务回滚问题。
错误示例:SendWelcomeEmail::dispatch($user) 在 created 里调用,但用户创建事务还没 commit,队列进程取不到最新数据。
- 用
afterCommit()包裹队列分发:SendWelcomeEmail::dispatch($user)->afterCommit(),确保事务提交后再执行 - 避免在
saving或updating中 dispatch 队列 —— 这些钩子可能被多次触发(比如批量更新),导致重复任务 - 如果观察者要读取关系数据(如
$user->profile),先用$user->load('profile')预加载,别等队列里再查,否则关联数据可能已被修改或软删除
为什么 observer 里不能用 $this->app 或 Facade 获取当前请求上下文
模型观察者是无状态的,它可能在命令行、队列、HTTP 请求中任意环境触发。试图在 UserObserver::created() 里调用 request()->ip() 或 auth()->id(),90% 情况下返回 null 或报错。
典型错误信息:Call to a member function ip() on null 或 Auth facade is not bound。
- 观察者不持有请求上下文,
request()和auth()在非 HTTP 环境下不可用 - 如果业务真需要记录操作人,应在业务层(如 Controller 或 Service)显式传入 user_id 或 ip,再存为模型字段,观察者只负责消费这个字段
- 不要在观察者里做「当前登录用户」相关的权限校验 —— 校验逻辑属于应用层,不是数据持久化层的事











