Laravel 事件与监听器

Laravel 的事件系统提供了一种优雅的方式来实现观察者模式,让你可以在应用中定义和触发事件,并通过监听器来响应这些事件。这种设计可以显著解耦业务逻辑,使代码更易于维护和扩展。本章将全面讲解如何创建事件、注册监听器、触发事件,以及如何利用队列和事件订阅者处理复杂场景。

🎯 核心思想: 当某些行为发生时(如用户注册、订单支付),我们“触发”一个事件,而“监听器”则负责处理该事件的后继操作(如发送欢迎邮件、更新库存)。事件与监听器之间通过服务容器绑定,无需在业务代码中硬编码。

1. 事件与监听器的工作流程

在 Laravel 中,事件系统的基本流程如下:

  1. 定义事件类(例如 UserRegistered),通常存放于 app/Events 目录。
  2. 定义监听器类(例如 SendWelcomeEmail),存放于 app/Listeners 目录。
  3. EventServiceProvider$listen 数组中注册事件与监听器的映射。
  4. 在需要的地方使用 event(new UserRegistered($user)) 触发事件。
  5. Laravel 自动调用所有注册的监听器,按顺序执行。

2. 生成事件与监听器

Laravel 提供了 Artisan 命令快速生成事件和监听器。

生成事件类


php artisan make:event UserRegistered
                    

这会在 app/Events/UserRegistered.php 创建事件类。通常事件类只需包含相关数据,例如:


<?php

namespace App\Events;

use App\Models\User;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class UserRegistered
{
    use Dispatchable, SerializesModels;

    public $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }
}
                    

生成监听器


php artisan make:listener SendWelcomeEmail --event=UserRegistered
                    

生成的监听器位于 app/Listeners/SendWelcomeEmail.php,其中包含 handle 方法,该方法接收事件对象作为参数:


<?php

namespace App\Listeners;

use App\Events\UserRegistered;
use Illuminate\Support\Facades\Mail;

class SendWelcomeEmail
{
    public function handle(UserRegistered $event)
    {
        // 发送欢迎邮件
        Mail::to($event->user->email)->send(new WelcomeMail($event->user));
    }
}
                    

3. 注册事件与监听器映射

app/Providers/EventServiceProvider.php$listen 数组中定义事件与监听器的关系:


protected $listen = [
    \App\Events\UserRegistered::class => [
        \App\Listeners\SendWelcomeEmail::class,
        \App\Listeners\AssignDefaultRole::class,
        // 可以为一个事件添加多个监听器
    ],
];
                    

注册后,使用 php artisan event:cache 可缓存事件映射(生产环境推荐),开发环境也可以不缓存,Laravel 会自动扫描。

4. 触发事件

使用全局辅助函数 event()Event Facade 触发事件:


use App\Events\UserRegistered;

// 在控制器或任意位置
$user = User::create([...]);
event(new UserRegistered($user));

// 或使用 Facade
use Illuminate\Support\Facades\Event;
Event::dispatch(new UserRegistered($user));
                    

触发事件后,Laravel 会依次调用所有注册的监听器的 handle 方法。

5. 使用队列异步处理监听器

如果某个监听器执行耗时操作(如发送邮件、调用外部 API),可以将其放入队列,避免阻塞主请求。只需让监听器类实现 ShouldQueue 接口:


use Illuminate\Contracts\Queue\ShouldQueue;

class SendWelcomeEmail implements ShouldQueue
{
    // ...
}
                    

Laravel 会自动将监听器推送到队列。你还可以在监听器中设置队列连接、延迟等:


public $queue = 'emails'; // 指定队列名称
public $delay = 60;       // 延迟60秒执行
                    
⚠️ 注意: 队列监听器需要在事件类中使用 SerializesModels trait,以确保模型对象能被正确序列化。Laravel 会使用模型的主键来重建实例,避免序列化完整模型。

6. 事件订阅者

当同一个类需要监听多个事件时,可以使用事件订阅者来组织逻辑。生成订阅者:


php artisan make:listener UserEventSubscriber --event
                    

订阅者类需包含 subscribe 方法,在其中注册事件监听:


namespace App\Listeners;

use Illuminate\Events\Dispatcher;

class UserEventSubscriber
{
    public function handleUserRegistered($event) { ... }
    public function handleUserLogin($event) { ... }

    public function subscribe(Dispatcher $events)
    {
        $events->listen(
            'App\Events\UserRegistered',
            [UserEventSubscriber::class, 'handleUserRegistered']
        );
        $events->listen(
            'App\Events\UserLogin',
            [UserEventSubscriber::class, 'handleUserLogin']
        );
    }
}
                    

然后在 EventServiceProvider$subscribe 属性中注册订阅者:


protected $subscribe = [
    \App\Listeners\UserEventSubscriber::class,
];
                    

7. Laravel 内置事件

Laravel 自身会触发许多事件,例如 Illuminate\Auth\Events\LoginIlluminate\Auth\Events\RegisteredIlluminate\Queue\Events\JobProcessed 等。你可以监听这些事件来实现扩展功能。例如,在用户登录后记录日志:


protected $listen = [
    'Illuminate\Auth\Events\Login' => [
        'App\Listeners\LogSuccessfulLogin',
    ],
];
                    

8. 事件与模型事件的对比

Laravel 模型也有自己的事件系统(如 createdupdateddeleted)。模型事件适用于与 Eloquent 模型生命周期相关的操作,而通用事件系统更适合跨模块的业务事件。你可以根据需要选择。


// 模型事件示例(在模型中定义)
protected static function booted()
{
    static::created(function ($user) {
        // 用户创建后执行
    });
}
                    

9. 最佳实践与注意事项

  • 事件命名: 使用过去时态命名事件,如 OrderPaidUserLoggedIn,清晰表达发生的事情。
  • 避免在监听器中包含业务逻辑: 监听器应只负责响应事件,复杂逻辑可调用服务类。
  • 队列监听器错误处理: 使用 failed 方法处理失败的队列任务,或配置重试次数。
  • 事件缓存: 生产环境运行 php artisan event:cache 提升性能,开发环境修改事件映射后需重新缓存。
  • 测试事件: 可以使用 Event::fake() 在测试中断言事件是否被触发。

测试事件示例:


use Illuminate\Support\Facades\Event;
use App\Events\UserRegistered;

public function test_user_registered_event_is_dispatched()
{
    Event::fake();

    // 执行注册逻辑
    $response = $this->post('/register', [...]);

    Event::assertDispatched(UserRegistered::class);
}
                    

10. 总结

Laravel 的事件系统为应用提供了灵活的扩展点,通过事件和监听器,你可以将核心业务逻辑与辅助操作(如日志、通知、缓存更新)解耦。结合队列,你可以异步处理耗时任务,提升应用响应速度。掌握事件系统是构建大型 Laravel 应用的关键技能之一。

📖 扩展阅读: 官方文档 EventsQueues 提供了更深入的配置和高级用法。