模型事件默认同步执行,易因监听器中i/o或复杂逻辑阻塞响应;应延迟初始化、动态预加载、防内存泄漏、区分广播事件,并及时更新事件缓存。

模型事件(如 creating、saved、retrieved)默认同步执行,一旦监听器里有数据库查询、HTTP 调用或复杂逻辑,就会拖慢主请求。这不是“可以接受的延迟”,是直接阻塞整个响应周期。
模型事件监听器别在构造函数里做初始化
很多开发者把 SDK 客户端、配置读取或连接池初始化写在监听器构造函数里,结果每次事件触发都 new 一次实例——哪怕该监听器只在后台任务中用到一次。
- 构造函数中的 I/O(如
file_get_contents、Redis::connect())会随事件高频重复执行 - 依赖注入容器解析时若带 heavy service(如大数组配置、加密密钥加载),也会放大开销
- 正确做法:把初始化逻辑移到
handle()内部,用static $client = null;做单例缓存,或直接判断$event->user_id是否为有效值再执行
用 retrieved 事件做条件预加载,但别碰 $with
全局 $with 是最隐蔽的性能陷阱之一:它强制对所有模型实例加载关联,哪怕 $model->domain_id === null 也照查不误。
- 移除
User::$with = ['domain']这类静态声明 - 改用
retrieved事件,在模型取出后动态判断:if ($user->domain_id) { $user->load('domain'); } - 注意:不要在
retrieved里调$user->load('domain')后又立即访问$user->domain->name—— 若未加isset($user->relationLoaded['domain'])判断,可能触发二次查询
事务中触发模型事件要防内存泄漏
批量处理数据时(比如导入 10 万用户),每个 created 或 updated 事件都会触发监听器,而监听器若没清理引用,PHP GC 不会及时回收。
- 在事务开头禁用无关事件:
Event::forget('eloquent.created: App\Models\Log'); - 避免在监听器里保存整个模型实例到静态变量或全局数组
- 如果监听器必须记录日志,用
DB::table('logs')->insert([...])替代Log::create(...),绕过 Eloquent 实例化开销
广播事件和模型事件别混用
有人把 ShouldBroadcast 接口直接加在模型事件类上(比如 UserRegistered),以为这样就能自动推送到前端——但模型事件本身不走广播通道,ShouldBroadcast 在这里完全无效。
- 模型事件(
created、updated)只用于服务端内部响应,不序列化、不发 Redis Pub/Sub - 要广播,得另建一个实现
ShouldBroadcast的独立事件类,并在模型监听器里手动 dispatch:event(new UserRegisteredBroadcast($user)); - 否则你会看到
event:cache生效了,但前端 Echo 一直收不到消息——因为根本没进广播队列
最常被跳过的其实是事件注册时机:改完 EventServiceProvider::$listen 后不跑 php artisan event:cache,生产环境就等于没注册;而开发时开着缓存又不清理,会导致新写的监听器死活不触发,且无任何错误提示。











