监听器执行顺序由$listen数组下标决定,Laravel 9+支持$priority属性覆盖默认顺序,同步事件中return false可中断传播,模型静态监听器总在事件服务提供者监听器之前执行。

监听器执行顺序由 $listen 数组下标决定
在 EventServiceProvider 中,$listen 数组的书写顺序就是运行顺序——没有隐藏权重、不看类名字母序、也不受文件加载影响。比如:
protected $listen = [
'App\Events\OrderCreated' => [
'App\Listeners\ValidateOrderBeforeSave',
'App\Listeners\HashOrderItems',
'App\Listeners\SendOrderConfirmation',
],
];
上面三个监听器会严格按数组索引 0→1→2 执行。
- 改顺序最安全的方式:直接剪切粘贴数组项,把关键监听器(如校验)移到最前面
- 别依赖“自动发现”或 composer dump-autoload 来控制顺序——它只影响类加载,不干预注册时机
- 如果启用了事件缓存(
php artisan event:cache),改完必须重新运行该命令,否则旧顺序仍生效
用 $priority 属性覆盖默认顺序(Laravel 9+)
当不能或不便调整 $listen 数组时,可在监听器类中定义 $priority 属性,数值越大越先执行:
class SendOrderConfirmation
{
public $priority = 5;
public function handle(OrderCreated $event) { ... }
}
class ValidateOrderBeforeSave
{
public $priority = 10; // 这个会先于上面那个执行
public function handle(OrderCreated $event) { ... }
}
注意:
-
$priority只对通过$listen注册的类监听器有效;模型内static::creating()或Event::listen()闭包不识别该属性 - 优先级相同则回落到数组顺序
- 框架内部按
usort降序排列,负数也合法(如-5表示低优先级)
return false 只在同步事件中停止传播
监听器 handle() 方法返回 false,能立即中断后续监听器执行——但仅限同步场景:
public function handle(RegisterUserEvent $event)
{
if (! $this->validate($event->email)) {
return false; // ✅ 后续监听器不会运行
}
// ...
}
这个机制在队列任务中完全失效,因为每个监听器被 dispatch 到队列后是独立进程,无法互相感知返回值。
- 异步环境下,要用事务 + 事件重发控制流,而不是依赖
return false - 若监听器实现了
ShouldQueue,哪怕事件本身是同步触发的,该监听器也会进队列,return false失效 - 日志里看到 “Stopped propagation” 但后续监听器还在跑?八成是某个监听器进了队列
模型静态监听器和事件服务提供者监听器互不干扰
static::saving() 这类模型内监听器,和 EventServiceProvider 里的类监听器,是两套注册系统:
- 模型静态监听器总在所有事件服务提供者监听器之前执行,且无法被后者“插队”
- 它们之间不共享
$priority,也不受$listen数组顺序影响 - 常见冲突:模型里
static::saving()填了$model->slug,而UserObserver的saving()又清掉了它——这不是顺序问题,是设计冗余
真正难调试的,往往是这两套机制混用时字段被反复覆盖,而不是某一个监听器“没抢到优先级”。











