Laravel 13 事件系统进阶
Laravel 的事件系统实现了观察者模式,允许在应用程序中订阅和监听各种事件。本文将深入探讨 Laravel 13 事件系统的高级用法。
定义事件
创建事件类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php
namespace App\Events;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; use App\Models\Order;
class OrderShipped { use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct( public Order $order, public string $trackingNumber, public ?string $notes = null ) {} }
|
使用 SerializesModels
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php
namespace App\Events;
use Illuminate\SerializesModels; use App\Models\User;
class UserRegistered { use SerializesModels;
public function __construct( public User $user ) {} }
|
定义监听器
创建监听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <?php
namespace App\Listeners;
use App\Events\OrderShipped; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; use App\Services\ShippingService;
class SendShipmentNotification implements ShouldQueue { use InteractsWithQueue;
public function __construct( private ShippingService $shippingService ) {}
public function handle(OrderShipped $event): void { $this->shippingService->sendNotification( $event->order, $event->trackingNumber ); }
public function failed(OrderShipped $event, \Throwable $exception): void { \Log::error('Shipment notification failed', [ 'order_id' => $event->order->id, 'error' => $exception->getMessage(), ]); } }
|
队列监听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <?php
namespace App\Listeners;
use App\Events\UserRegistered; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue;
class SendWelcomeEmail implements ShouldQueue { use InteractsWithQueue;
public string $queue = 'emails';
public int $delay = 60;
public int $tries = 3;
public int $backoff = [10, 30, 60];
public bool $shouldFailOnTimeout = true;
public function handle(UserRegistered $event): void { \Mail::to($event->user)->send(new WelcomeEmail($event->user)); }
public function shouldQueue(UserRegistered $event): bool { return $event->user->email_verified; }
public function viaConnection(): string { return 'redis'; }
public function viaQueue(): string { return 'low-priority'; } }
|
注册事件和监听器
在 EventServiceProvider 中注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <?php
namespace App\Providers;
use Illuminate\Auth\Events\Login; use Illuminate\Auth\Events\Logout; use App\Events\OrderShipped; use App\Listeners\SendShipmentNotification; use App\Listeners\UpdateUserLoginTime; use App\Listeners\ClearUserSession; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider { protected $listen = [ OrderShipped::class => [ SendShipmentNotification::class, UpdateInventory::class, LogShipment::class, ], Login::class => [ UpdateUserLoginTime::class, ], Logout::class => [ ClearUserSession::class, ], ];
protected $subscribe = [ UserEventSubscriber::class, ]; }
|
自动发现
1 2 3 4 5 6 7 8 9 10 11 12 13
| public function shouldDiscoverEvents(): bool { return true; }
protected function discoverEventsWithin(): array { return [ $this->app->path('Listeners'), $this->app->path('Modules/*/Listeners'), ]; }
|
分发事件
基本分发
1 2 3 4 5 6 7 8 9 10
| use App\Events\OrderShipped; use App\Models\Order;
$order = Order::find(1);
OrderShipped::dispatch($order, 'TRACK123');
event(new OrderShipped($order, 'TRACK123'));
Order::find(1)->dispatch(new OrderShipped($order));
|
条件分发
1 2 3 4 5 6 7 8
| use App\Events\OrderShipped;
if ($order->isReady()) { OrderShipped::dispatch($order); }
OrderShipped::dispatchIf($order->isReady(), $order); OrderShipped::dispatchUnless($order->isCancelled(), $order);
|
事件订阅者
创建订阅者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <?php
namespace App\Listeners;
use Illuminate\Auth\Events\Login; use Illuminate\Auth\Events\Logout; use Illuminate\Events\Dispatcher;
class UserEventSubscriber { public function handleUserLogin(Login $event): void { \Log::info('User logged in', ['user_id' => $event->user->id]); }
public function handleUserLogout(Logout $event): void { \Log::info('User logged out', ['user_id' => $event->user->id]); }
public function subscribe(Dispatcher $events): array { return [ Login::class => 'handleUserLogin', Logout::class => 'handleUserLogout', ]; } }
|
事件监听器队列
配置队列连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php
namespace App\Listeners;
use App\Events\PodcastProcessed; use Illuminate\Contracts\Queue\ShouldQueue;
class ProcessPodcast implements ShouldQueue { public $connection = 'redis';
public $queue = 'podcasts';
public $delay = 10;
public function handle(PodcastProcessed $event): void { } }
|
手动释放作业
1 2 3 4 5 6 7 8 9
| public function handle(PodcastProcessed $event): void { if ($event->podcast->isLocked()) { $this->release(30); return; }
$event->podcast->process(); }
|
模型事件
模型事件钩子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model { protected static function booted(): void { static::creating(function (User $user) { $user->uuid = (string) \Str::uuid(); });
static::created(function (User $user) { $user->profile()->create([]); });
static::updating(function (User $user) { if ($user->isDirty('email')) { $user->email_verified_at = null; } });
static::deleting(function (User $user) { $user->posts()->delete(); $user->comments()->delete(); });
static::deleted(function (User $user) { \Storage::delete($user->avatar); }); } }
|
使用观察者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <?php
namespace App\Observers;
use App\Models\User;
class UserObserver { public function creating(User $user): void { $user->uuid = (string) \Str::uuid(); }
public function created(User $user): void { $user->profile()->create([]); }
public function updating(User $user): void { if ($user->isDirty('email')) { $user->email_verified_at = null; } }
public function deleting(User $user): void { $user->posts()->delete(); }
public function deleted(User $user): void { \Storage::delete($user->avatar); }
public function restored(User $user): void { $user->posts()->withTrashed()->restore(); }
public function forceDeleted(User $user): void { $user->posts()->forceDelete(); } }
|
注册观察者
1 2 3 4 5 6 7 8 9 10 11 12 13
| use App\Models\User; use App\Observers\UserObserver;
public function boot(): void { User::observe(UserObserver::class); }
protected $observers = [ User::class => [UserObserver::class], ];
|
静默模型事件
1 2 3 4 5 6 7 8 9 10 11 12
| use App\Models\User;
User::withoutEvents(function () { User::factory()->count(10)->create(); });
$user = User::withoutEvents(function () { $user = User::find(1); $user->name = 'New Name'; $user->save(); return $user; });
|
自定义事件
广播事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <?php
namespace App\Events;
use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\PresenceChannel; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels;
class ServerCreated implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct( public array $server ) {}
public function broadcastOn(): array { return [ new PrivateChannel('user.'.$this->server['user_id']), ]; }
public function broadcastAs(): string { return 'server.created'; }
public function broadcastWith(): array { return [ 'id' => $this->server['id'], 'name' => $this->server['name'], ]; }
public function broadcastQueue(): string { return 'broadcasts'; } }
|
测试事件
断言事件被分发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <?php
namespace Tests\Feature;
use Tests\TestCase; use App\Events\OrderShipped; use App\Models\Order; use Illuminate\Support\Facades\Event;
class OrderTest extends TestCase { public function test_order_shipped_event_is_dispatched(): void { Event::fake();
$order = Order::factory()->create();
$this->postJson("/api/orders/{$order->id}/ship");
Event::assertDispatched(OrderShipped::class); Event::assertDispatched(OrderShipped::class, 1); Event::assertDispatched(function (OrderShipped $event) use ($order) { return $event->order->id === $order->id; }); Event::assertNotDispatched(OrderFailed::class); }
public function test_specific_events(): void { Event::fake([OrderShipped::class]);
Event::assertDispatched(OrderShipped::class); } }
|
断言监听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public function test_listener_handles_event(): void { Event::fake();
$order = Order::factory()->create(); $event = new OrderShipped($order, 'TRACK123');
$listener = new SendShipmentNotification( $this->mock(ShippingService::class) );
$listener->handle($event);
Event::assertNotDispatched(OrderShipped::class); }
|
最佳实践
1. 使用事件解耦
1 2 3 4 5 6 7 8 9 10 11 12
| OrderShipped::dispatch($order);
class SendShipmentNotification { } class UpdateInventory { } class LogShipment { }
$order->sendNotification(); $order->updateInventory(); $order->logShipment();
|
2. 合理使用队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class SendShipmentNotification implements ShouldQueue { public function handle(OrderShipped $event): void { \Mail::to($event->order->user)->send(new ShipmentEmail()); } }
class UpdateOrderStatus { public function handle(OrderShipped $event): void { $event->order->update(['status' => 'shipped']); } }
|
3. 使用 SerializesModels
1 2 3 4 5 6 7 8 9
| class OrderShipped { use SerializesModels;
public function __construct(public Order $order) {} }
|
总结
Laravel 13 的事件系统提供了强大而灵活的事件处理能力。通过合理使用事件和监听器,可以实现应用程序的解耦,提高代码的可维护性和可扩展性。记住使用队列处理耗时操作,利用模型观察者简化模型事件处理,并通过事件广播实现实时功能。