Event / Listener / Observer 模式
事件系统让代码解耦:任务完成时,不需要在 TaskController 里写"发邮件、更新统计、推送通知"——只需要触发 TaskCompleted 事件,各个关心这个事件的 Listener 自行处理。新增功能不需要修改 Controller。
三者的关系
Event(事件):某件事发生了(TaskCompleted、UserRegistered)
Listener(监听器):当某事件发生时要做什么(发邮件、更新统计)
Observer(观察者):专门监听 Eloquent 模型生命周期的 Listener
Event ──→ EventDispatcher ──→ Listener A(发邮件)
──→ Listener B(更新积分)
──→ Listener C(推送通知)
创建 Event 和 Listener
php artisan make:event TaskCompleted
php artisan make:listener SendTaskCompletedNotification --event=TaskCompleted
php artisan make:listener UpdateProjectProgress --event=TaskCompleted
<?php
// app/Events/TaskCompleted.php
namespace App\Events;
use App\Models\Task;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class TaskCompleted
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(
public readonly Task $task
) {}
}
<?php
// app/Listeners/SendTaskCompletedNotification.php
namespace App\Listeners;
use App\Events\TaskCompleted;
use App\Notifications\TaskCompletedNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
// 实现 ShouldQueue → Listener 异步执行(推入队列)
class SendTaskCompletedNotification implements ShouldQueue
{
public string $queue = 'notifications'; // 指定队列
public function handle(TaskCompleted $event): void
{
$event->task->user->notify(
new TaskCompletedNotification($event->task)
);
}
// 监听失败后不重试
public function failed(TaskCompleted $event, \Throwable $exception): void
{
logger()->warning("Failed to send task completed notification: {$exception->getMessage()}");
}
}
注册事件(Laravel 11 自动发现 or 手动)
// Laravel 会自动扫描 app/Listeners/ 并根据类型提示匹配事件
// 无需手动注册(Laravel 11 默认开启 Event Discovery)
// 如果需要手动注册(精确控制),在 AppServiceProvider::boot() 中:
use Illuminate\Support\Facades\Event;
Event::listen(
TaskCompleted::class,
[SendTaskCompletedNotification::class, 'handle']
);
// 闭包监听(适合简单逻辑)
Event::listen(TaskCompleted::class, function (TaskCompleted $event) {
$event->task->project?->recalculateProgress();
});
触发事件
// 方式一:event() 辅助函数
event(new TaskCompleted($task));
// 方式二:Event Facade
\Illuminate\Support\Facades\Event::dispatch(new TaskCompleted($task));
// 方式三:在模型上调用(如果模型 use Dispatchable)
TaskCompleted::dispatch($task);
Observer:监听 Eloquent 生命周期
php artisan make:observer TaskObserver --model=Task
<?php
// app/Observers/TaskObserver.php
namespace App\Observers;
use App\Models\Task;
use App\Events\TaskCompleted;
class TaskObserver
{
// 创建前
public function creating(Task $task): void
{
$task->user_id = $task->user_id ?? auth()->id();
}
// 创建后
public function created(Task $task): void
{
// 更新用户的任务统计缓存
cache()->forget("user.{$task->user_id}.task_stats");
}
// 更新后
public function updated(Task $task): void
{
// 任务状态变为完成时触发事件
if ($task->wasChanged('status') && $task->status === 'done') {
event(new TaskCompleted($task));
}
// 清除缓存
cache()->forget("task.{$task->id}");
}
// 删除前
public function deleting(Task $task): void
{
// 级联删除相关文件
$task->attachments->each(fn($attachment) => Storage::delete($attachment->path));
}
// Eloquent 生命周期方法:
// creating, created(创建前后)
// updating, updated(更新前后)
// saving, saved(创建或更新前后)
// deleting, deleted(删除前后)
// restoring, restored(软删除恢复前后)
// retrieved(从数据库查询后)
}
// 注册 Observer(AppServiceProvider::boot())
Task::observe(TaskObserver::class);
事件与队列的关系
graph TD
A["Controller\nevent(TaskCompleted)"] --> B["EventDispatcher"]
B --> C["Listener A\n(ShouldQueue)"]
B --> D["Listener B\n(ShouldQueue)"]
B --> E["Listener C\n(同步执行)"]
C --> F["Redis Queue\n异步执行"]
D --> F
E --> G["立即执行"]
// Listener 是否异步:实现 ShouldQueue 接口决定
class SendEmailListener implements ShouldQueue // 异步
class UpdateCacheListener // 同步(无 ShouldQueue)
真实场景:TaskFlow 事件链
// 任务完成 → 1. 发通知(异步) 2. 更新项目进度(同步) 3. 发 WebSocket 推送(广播)
// app/Events/TaskCompleted.php
class TaskCompleted implements ShouldBroadcast // 同时广播到 WebSocket
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(public readonly Task $task) {}
// 广播到哪个频道(见第7章第2节)
public function broadcastOn(): array
{
return [
new PrivateChannel('user.' . $this->task->user_id),
new PresenceChannel('project.' . $this->task->project_id),
];
}
// 广播的数据
public function broadcastWith(): array
{
return [
'task_id' => $this->task->id,
'title' => $this->task->title,
'completed' => true,
];
}
}
下一节:Laravel Reverb:原生 WebSocket——事件触发了广播,下一步是搭建 WebSocket 服务。Laravel 11 引入的 Reverb 是官方原生 WebSocket 服务器,不再依赖 Pusher。