Laravel 事件系统使用说明

👉 什么是事件系统?

Laravel 的事件系统基于发布-订阅(Pub/Sub)模式,核心由两部分组成:

事件(Event):发生了什么事情,例如 UserRegistered(用户注册了)

监听器(Listener):监听并响应这个事件,例如 SendWelcomeEmail

你定义事件类 → 触发事件 → 所有关联的监听器会被执行。

事件系统的使用步骤

本文以 laravel 11 为例讲解

1、创建事件类

php artisan make:event UserRegistered

会生成文件:

// app/Events/UserRegistered.php

namespace App\Events;

use App\Models\User;

class UserRegistered
{
    public function __construct(public User $user) {}
}

2、创建监听器类

php artisan make:listener SendWelcomeEmail --event=UserRegistered

会生成:

<?php
// app/Listeners/SendWelcomeEmail.php

namespace App\Listeners;

use App\Events\UserRegistered;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;

class SendWelcomeEmail
{
    /**
     * Create the event listener.
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     */
    public function handle(UserRegistered $event): void
    {
        // 访问 $event->user,发送邮件或通知
        \Log::info('Send welcome email to ' . $event->user->email);
    }
}

在 Laravel 11 中,事件触发后监听器默认是同步执行的,触发事件后 handle() 方法会在同一个请求内执行完毕,只有执行完成后,主程序才继续。

你也可以实现 ShouldQueue 接口让其异步执行:

class SendWelcomeEmail implements ShouldQueue

Laravel 会自动将该监听器推送到队列,事件触发后主请求立即返回,监听器稍后由队列 worker 执行。


3、注册事件与监听器

默认情况下,Laravel 会扫描应用内的 Listeners 目录自动发现和注册事件监听器。不需要主动做事件和监听的绑定。当 Laravel 发现监听器的类里面有以 handle 或 __invoke 开头的方法,Laravel 会将这些方法注册为监听器,对应的事件为方法签名中的事件类, 如上面监听类中的 public function handle(UserRegistered $event): void 定义为这个监听类监听的事件是 UserRegistered

但若某个事件有多个监听类,我们可以主动注册事件监听,这样监听器会按照定义顺序执序,不需要考虑顺序的情况可以不定义。

注意个点:若像下面文件一样主动注册监听,监听器中handle方法中不要声明要监听的事件(去掉 UserRegistered $event 传参),否则这个监听器会执行两次;laravel 自动发现执行一次,主动注册又执行一次。shouldDiscoverEvents 设为 false 无效,不知道什么原因。

使用 EventServiceProvider 类主动注册事件监听:

// app/Providers/EventServiceProvider.php

use App\Events\UserRegistered;
use App\Listeners\SendWelcomeEmail;

protected $listen = [
    UserRegistered::class => [ # 事件
        SendWelcomeEmail::class, # 可添加多个监听器,按从上到下的顺序执行
    ],
];

注意 Laravel 11 不会自动加载 App\Providers\EventServiceProvider::class, 需要在 bootstrap/providers.php 文件中手动引入

<?php

// bootstrap/providers.php

return [
    App\Providers\AppServiceProvider::class,
    App\Providers\EventServiceProvider::class,
];

然后运行:

php artisan event:cache

📌 Laravel 会缓存事件监听映射,提高性能。开发时如果修改监听器注册,需要重新执行该命令。


4、触发事件

use App\Events\UserRegistered;

event(new UserRegistered($user));

event() 是 Laravel 全局助手函数,也可以用 Event::dispatch()

监听器异步执行(可选)

若监听器比较耗时(如发邮件),建议实现:

use Illuminate\Contracts\Queue\ShouldQueue;

class SendWelcomeEmail implements ShouldQueue

并确保你已启动队列监听器:

php artisan queue:work

总结流程图

[ 用户注册 ]
      ↓
[ event(new UserRegistered($user)) ]
      ↓
[ UserRegistered Event ]
      ↓
[ SendWelcomeEmail Listener ]
      ↓
[ 邮件发送 / 记录日志 / 通知等 ]

停止事件传播

有时,当一个事件注册了多个监听器时,一个监听器执行后你可能希望停止将事件传播到其他监听器。你可以通过从监听器的 handle 方法中返回 false 来做到这一点。

但注意,它只适用于你使用 Event::until()(或 event()->until())方式触发事件的场景。

Laravel 的两种触发方式对返回值的处理不同:

调用方式 返回值 是否中止监听器传播
event() 所有返回值数组 ❌ 否(全部执行)
Event::dispatch() 同上 ❌ 否(全部执行)
Event::until() 首个非 null ✅ 是(中止执行)
event()->until() 同上 ✅ 是

until() 模式下,监听器返回 false 将停止后续监听器

事件和队列的区别

维度 Laravel 事件 Laravel 队列
模式 发布-订阅 生产者-消费者
解耦 ✅ 非常适合 ❌ 不是目标
异步 ❌ 默认同步(可配队列) ✅ 默认异步
多任务 ✅ 支持多个监听器 ✅ 但每个 Job 单一职责更好
典型用途 业务流程解耦(如:注册通知多个逻辑) 长耗时逻辑(如:生成 PDF、发邮件)
控制性 ❌ 无优先级、失败处理弱 ✅ 有 retry/delay/failure 等机制