事件类必须继承 Dispatchable trait,否则 Event() 或 dispatch() 不会触发;监听器需正确注册或符合自动发现规则;dispatch() 支持队列而 event() 仅同步;监听器应避免循环依赖和非序列化依赖。

事件类必须继承 IlluminateFoundationEventsDispatchable
不继承这个 trait 或不使用 use Dispatchable;,调用 event() 或 dispatch(new XxxEvent) 时事件不会被触发——laravel 内部靠它识别可分发事件。常见错误是手写空类,忘了加这行:
use IlluminateFoundationEventsDispatchable; class OrderShipped { use Dispatchable; public $order; public function __construct($order) { $this->order = $order; } }
注意:php 8.0+ 可用构造器属性提升,但 Dispatchable 仍不可省略;Laravel 10+ 默认事件类由 php artisan make:event 自动生成并已包含该 trait。
监听器注册位置决定是否自动发现
Laravel 5.8+ 默认启用事件自动发现(EventServiceProvider 中 $listen 为空且 shouldDiscoverEvents() 返回 true),但依赖 composer dump-autoload -o 和命名空间约定:appListenersXxx 对应 AppEventsXxxEvent。一旦命名不符或类未被 Composer 加载,监听器就静默失效。
更稳妥的做法是显式注册:
protected $listen = [ 'AppEventsOrderShipped' => [ 'AppListenersSendShipmentNotification', 'AppListenersUpdateInventory', ], ];
- 监听器类必须存在且可自动加载(检查
composer dump-autoload是否执行) - 数组值支持字符串(FQCN)或闭包,但闭包无法序列化,不能用于队列监听器
- 若监听器需队列处理,必须实现
ShouldQueue接口,且不能含非序列化依赖(如Request、session)
event() 同步触发 vs dispatch(new XxxEvent) 的差异
两者都可触发事件,但行为不同:
-
event(new OrderShipped($order)):纯同步,不走队列,无视监听器是否实现了ShouldQueue -
dispatch(new OrderShipped($order)):本质是分发 job,若监听器实现了ShouldQueue,则进入队列;否则退化为同步调用
典型陷阱:在命令行或测试中用 event() 测试监听器,结果监听器里的队列逻辑没执行,误以为监听失败。正确做法是统一用 dispatch(),或确保环境已配置队列驱动(如 sync 驱动用于开发)。
监听器里访问容器实例要小心循环依赖
监听器构造函数注入服务(如 Mailer、Notification)通常没问题,但若该服务又监听了同一事件或依赖了事件调度器,就可能触发递归调用甚至内存溢出。例如:
class SendShipmentNotification { public function __construct(public Mailer $mailer) {} public function handle(OrderShipped $event) { // 若 Mailer 内部触发了另一个事件,而该事件又被当前监听器处理…… $this->mailer->to(...)->send(...); } }
排查方法:开启 Laravel 日志级别为 debug,观察日志中是否反复出现同一事件名;生产环境建议监听器保持轻量,复杂逻辑抽到 service 类中,并避免在监听器里手动触发新事件。
事件机制本身不难,但隐式依赖和队列边界最容易出问题——别假设“注册了就能跑”,每次加新监听器后,至少用 php artisan event:cache --no-interaction 清一次缓存再验证。