> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 事件与监听器

> 学习使用 Laravel 事件系统让应用组件保持松耦合。

## 什么是事件

Laravel 的事件系统实现了简单的观察者模式。
在应用中触发（dispatch）事件，再定义响应这些事件的监听器，可以把组件间的耦合降到最小。

例如触发「订单已确认」事件后，「发送确认邮件」「减少库存」「向 Slack 通知」等多个监听器可以独立运行。
订单处理代码完全不需要知道邮件或 Slack 的实现。

<Info>
  事件类放在 `app/Events`，监听器放在 `app/Listeners`。
  目录不存在时 Artisan 命令会自动创建。
</Info>

```mermaid theme={null}
flowchart TD
    A["触发事件<br>dispatch()"] --> B["事件分发器"]
    B --> C["监听器 1<br>(同步)"]
    B --> D["监听器 2<br>(同步)"]
    B --> E["监听器 3<br>ShouldQueue"]
    C --> F["立即执行"]
    D --> G["立即执行"]
    E --> H["加入队列"]
    H --> I["worker 异步执行"]
```

## 生成事件与监听器

```bash theme={null}
php artisan make:event UserRegistered

php artisan make:listener SendWelcomeEmail --event=UserRegistered
```

不带参数运行会进入交互式提示：

```bash theme={null}
php artisan make:event

php artisan make:listener
```

## 注册事件

### 事件自动发现

默认情况下，Laravel 会扫描 `app/Listeners` 自动注册监听器。
它会读取 `handle` 或 `__invoke` 方法的参数类型来推断事件映射。

```php theme={null}
use App\Events\UserRegistered;

class SendWelcomeEmail
{
    public function handle(UserRegistered $event): void
    {
        // 发送欢迎邮件
    }
}
```

使用 PHP union 类型可以让同一方法处理多个事件：

```php theme={null}
public function handle(UserRegistered|UserUpdated $event): void
{
    // ...
}
```

若监听器放在其他目录，在 `bootstrap/app.php` 中额外指定扫描位置：

```php theme={null}
->withEvents(discover: [
    __DIR__.'/../app/Domain/Orders/Listeners',
])
```

通配符可指定多个目录：

```php theme={null}
->withEvents(discover: [
    __DIR__.'/../app/Domain/*/Listeners',
])
```

查看已注册的监听器：

```bash theme={null}
php artisan event:list
```

<Tip>
  生产环境建议缓存监听器清单以提升性能。部署时运行 `php artisan optimize` 或 `php artisan event:cache`。
  清除缓存用 `php artisan event:clear`。
</Tip>

### 手动注册

也可以在 `AppServiceProvider` 的 `boot` 中通过 `Event` 门面手动注册：

```php theme={null}
use App\Events\UserRegistered;
use App\Listeners\SendWelcomeEmail;
use Illuminate\Support\Facades\Event;

public function boot(): void
{
    Event::listen(
        UserRegistered::class,
        SendWelcomeEmail::class,
    );
}
```

也可以用闭包：

```php theme={null}
use App\Events\UserRegistered;
use Illuminate\Support\Facades\Event;

public function boot(): void
{
    Event::listen(function (UserRegistered $event) {
        // ...
    });
}
```

## 定义事件

事件类是数据的容器，不含逻辑，只以属性形式承载信息：

```php theme={null}
<?php

namespace App\Events;

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

class UserRegistered
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public User $user,
    ) {}
}
```

`SerializesModels` trait 让排队的监听器在序列化事件时能正确处理 Eloquent 模型。

## 触发事件

使用 `dispatch` 静态方法或 `event()` 辅助函数触发事件：

```php theme={null}
use App\Events\UserRegistered;

// 静态方法
UserRegistered::dispatch($user);

// 辅助函数
event(new UserRegistered($user));
```

也有按条件触发的方法：

```php theme={null}
UserRegistered::dispatchIf($condition, $user);

UserRegistered::dispatchUnless($condition, $user);
```

### 事务提交后触发

若希望仅在数据库事务提交后触发，让事件类实现 `ShouldDispatchAfterCommit`。
事务失败则事件会被丢弃：

```php theme={null}
<?php

namespace App\Events;

use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class UserRegistered implements ShouldDispatchAfterCommit
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public User $user,
    ) {}
}
```

## 编写监听器

监听器通过 `handle` 方法接收事件。构造器中的依赖由服务容器自动注入：

```php theme={null}
<?php

namespace App\Listeners;

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

class SendWelcomeEmail
{
    public function __construct() {}

    public function handle(UserRegistered $event): void
    {
        Mail::to($event->user->email)
            ->send(new WelcomeMail($event->user));
    }
}
```

`handle` 方法返回 `false` 可以阻止事件继续传给后续监听器。

## 排队监听器

发送邮件、发起 HTTP 请求等耗时操作可以作为排队监听器异步执行。
只需实现 `ShouldQueue`，事件触发时监听器会自动入队。

```php theme={null}
<?php

namespace App\Listeners;

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

class SendWelcomeEmail implements ShouldQueue
{
    public function handle(UserRegistered $event): void
    {
        // 此逻辑由队列 worker 异步执行
    }
}
```

<Info>
  使用排队监听器前需配置队列并启动 worker。详见[队列与任务](/zh/queues)。
</Info>

### 自定义队列连接 / 名称 / 延迟

用 PHP 属性设置：

```php theme={null}
<?php

namespace App\Listeners;

use App\Events\UserRegistered;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\Delay;
use Illuminate\Queue\Attributes\Queue;

#[Connection('redis')]
#[Queue('emails')]
#[Delay(10)]
class SendWelcomeEmail implements ShouldQueue
{
    public function handle(UserRegistered $event): void
    {
        // ...
    }
}
```

也可以用方法动态返回：

```php theme={null}
public function viaConnection(): string
{
    return 'redis';
}

public function viaQueue(): string
{
    return 'emails';
}

public function withDelay(UserRegistered $event): int
{
    return 10;
}
```

### 最大重试次数与超时

用 `#[Tries]`、`#[Timeout]` 控制失败行为：

```php theme={null}
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\Attributes\Timeout;

#[Tries(3)]
#[Timeout(30)]
class SendWelcomeEmail implements ShouldQueue
{
    // ...
}
```

### 失败处理

定义 `failed` 方法处理超过最大重试仍失败的情况：

```php theme={null}
use Throwable;

public function failed(UserRegistered $event, Throwable $exception): void
{
    // 通知管理员等
}
```

## 事件订阅者

事件订阅者用来把相关的多个事件处理器组织在一个类中。

### 创建订阅者

`subscribe` 方法返回事件与处理函数的映射数组：

```php theme={null}
<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Events\Dispatcher;

class UserActivitySubscriber
{
    public function handleLogin(Login $event): void
    {
        // 登录处理
    }

    public function handleLogout(Logout $event): void
    {
        // 登出处理
    }

    /** @return array<string, string> */
    public function subscribe(Dispatcher $events): array
    {
        return [
            Login::class => 'handleLogin',
            Logout::class => 'handleLogout',
        ];
    }
}
```

### 注册订阅者

若启用事件自动发现，返回数组的 `subscribe` 会被自动注册。
手动注册在 `AppServiceProvider::boot` 中调用 `Event::subscribe`：

```php theme={null}
use App\Listeners\UserActivitySubscriber;
use Illuminate\Support\Facades\Event;

public function boot(): void
{
    Event::subscribe(UserActivitySubscriber::class);
}
```

## 实践示例：用户注册时发送欢迎邮件

<Steps>
  <Step title="创建事件类">
    ```bash theme={null}
    php artisan make:event UserRegistered
    ```

    编辑 `app/Events/UserRegistered.php` 添加用户属性：

    ```php theme={null}
    <?php

    namespace App\Events;

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

    class UserRegistered
    {
        use Dispatchable, InteractsWithSockets, SerializesModels;

        public function __construct(
            public User $user,
        ) {}
    }
    ```
  </Step>

  <Step title="创建监听器">
    ```bash theme={null}
    php artisan make:listener SendWelcomeEmail --event=UserRegistered
    ```

    实现 `ShouldQueue`，让发邮件异步处理：

    ```php theme={null}
    <?php

    namespace App\Listeners;

    use App\Events\UserRegistered;
    use App\Mail\WelcomeMail;
    use Illuminate\Contracts\Queue\ShouldQueue;
    use Illuminate\Support\Facades\Mail;

    class SendWelcomeEmail implements ShouldQueue
    {
        public function handle(UserRegistered $event): void
        {
            Mail::to($event->user->email)
                ->send(new WelcomeMail($event->user));
        }
    }
    ```
  </Step>

  <Step title="在控制器中触发">
    在用户注册后调用 `UserRegistered::dispatch()`：

    ```php theme={null}
    <?php

    namespace App\Http\Controllers\Auth;

    use App\Events\UserRegistered;
    use App\Models\User;
    use Illuminate\Http\RedirectResponse;
    use Illuminate\Http\Request;

    class RegisterController extends Controller
    {
        public function store(Request $request): RedirectResponse
        {
            $user = User::create($request->validated());

            UserRegistered::dispatch($user);

            return redirect('/dashboard');
        }
    }
    ```

    控制器只负责触发事件，不关心邮件发送的实现。
    未来若要加上 Slack 通知，控制器无需改动。
  </Step>

  <Step title="启动 worker">
    ```bash theme={null}
    php artisan queue:work
    ```
  </Step>
</Steps>

<Tip>
  启用事件自动发现后无需在 `AppServiceProvider` 中手动注册。放在 `app/Listeners` 中的类会被自动识别。
</Tip>

<Warning>
  使用 `php artisan event:list` 查看已注册的事件和监听器，定期检查是否有意外注册。
</Warning>


## Related topics

- [Laravel Telescope 实战技巧](/zh/blog/telescope-introduction.md)
- [Laravel 11 之后新应用结构 FAQ](/zh/advanced/app-structure-faq.md)
- [Laravel Reverb](/zh/reverb.md)
- [广播](/zh/broadcasting.md)
- [Laravel Telescope](/zh/telescope.md)
