Giới thiệu Events trong Laravel
Laravel event cung cấp cách thực thi pattern observer (người quan sát), cho phép bạn theo dõi và lắng nghe các sự kiện xảy ra trong ứng dụng. Event class được lưu trong thư mục app/Events, còn listener được lưu trong app/Listeners. 1 event có thể có nhiều listener không phụ thuộc vào nhau. Ví dụ bạn muốn gửi 1 thông báo slack cho user mỗi lần 1 đơn hàng được giao. Thay vì ghép mã code xử lý đơn hàng với code thông báo đến slack, bạn có thể tạo 1 event App\Events\OrderShipped và tạo listener để xử lý gửi thông báo.
Đăng ký Events và Listeners:
Đăng ký các event listeners trong App\Providers\EventServiceProvider. Thuộc tính listen bao gồm 1 array các sự kiện (keys) và listeners của nó (values). Ví dụ:
use App\Events\OrderShipped;
use App\Listeners\SendShipmentNotification;
/**
* The event listener mappings for the application.
*
* @var array<class-string, array<int, class-string>>
*/
protected $listen = [
OrderShipped::class => [
SendShipmentNotification::class,
],
];Lệnh event:list sẽ liệt kê các events và listeners được đăng ký.
Tạo events và listeners:
Lệnh sau sẽ tạo các events và listeners được đăng ký trong EventServiceProvider mà chưa tồn tại:
php artisan event:generateHoặc lệnh make:event và make:listener sẽ tạo các events và listeners cụ thể:
php artisan make:event PodcastProcessed
php artisan make:listener SendPodcastNotification --event=PodcastProcessedBạn cũng có thể đăng ký events class hoặc closure tại method boot của EventServiceProvider:
use App\Events\PodcastProcessed;
use App\Listeners\SendPodcastNotification;
use Illuminate\Support\Facades\Event;
/**
* Register any other events for your application.
*/
public function boot(): void
{
Event::listen(
PodcastProcessed::class,
SendPodcastNotification::class,
);
Event::listen(function (PodcastProcessed $event) {
// ...
});
}Queueable anonymous event listeners (trình lắng nghe sự kiện ẩn danh có thể xếp hàng đợi):
Khi đăng ký 1 listener dựa trên closure, bạn có thể bọc listener closure trong function Illuminate\Events\queueable để hướng dẫn Laravel thực thi listener sử dụng queue:
use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;
/**
* Register any other events for your application.
*/
public function boot(): void
{
Event::listen(queueable(function (PodcastProcessed $event) {
// ...
}));
}Giống queue jobs, bạn có thể sử dụng các method onConnection, onQueue và delay để tùy chỉnh thực thi của queued listener:
Event::listen(queueable(function (PodcastProcessed $event) {
// ...
})->onConnection('redis')->onQueue('podcasts')->delay(now()->addSeconds(10)));Nếu bạn muốn xử lý lỗi của anonymous queued listener, bạn có thể cung cấp 1 closure cho method catch khi define listener. Closure này sẽ nhận 1 event instance và Throwable instance mà gây ra lỗi của listener:
use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;
use Throwable;
Event::listen(queueable(function (PodcastProcessed $event) {
// ...
})->catch(function (PodcastProcessed $event, Throwable $e) {
// The queued listener failed...
}));Wildcard event listeners:
Bạn có thể đăng ký listener sử dụng * như 1 tham số, cho phép bạn xử lý nhiều event trong cùng 1 listener. Ví dụ:
Event::listen('event.*', function (string $eventName, array $data) {
// ...
});Event Discovery:
Thay vì đăng ký events và listeners bằng tay trong biến $listen của EventServiceProvider, bạn có thể tự động tìm sự kiện. Khi tính năng event discovery được bật, Laravel sẽ tự động tìm và đăng ký sự kiện và listener bằng cách quét thư mục Listeners. Ngoài ra, bất kỳ khai báo event nào trong EventServiceProvider vẫn sẽ được đăng ký.
Laravel tìm event listener bằng cách quét các class listener sử dụng PHP reflection. Khi Laravel tìm thấy bất kỳ listener method nào bắt đầu với handle hoặc __invoke, Laravel sẽ đăng ký những method đó là listener cho event mà được type-hint trong method:
use App\Events\PodcastProcessed;
class SendPodcastNotification
{
/**
* Handle the given event.
*/
public function handle(PodcastProcessed $event): void
{
// ...
}
}Event discovery được disable mặc định, bạn có thể bật bằng cách override method shouldDiscoverEvents của EventServiceProvider:
/**
* Determine if events and listeners should be automatically discovered.
*/
public function shouldDiscoverEvents(): bool
{
return true;
}Mặc định tất cả listeners trong thư mục app/Listeners sẽ được scan. Nếu bạn muốn bổ sung thêm thư mục để scan, override method discoverEventsWithin trong EventServiceProvider:
/**
* Get the listener directories that should be used to discover events.
*
* @return array<int, string>
*/
protected function discoverEventsWithin(): array
{
return [
$this->app->path('Listeners'),
];
}Event Discovery trong Production:
Ở production sẽ không hiệu quả khi để framework scan tất cả listeners mỗi lần request. Bạn cần chạy lệnh event:cache để cache tất cả events và listeners. Lệnh event:clear để xóa cache.
Tạo Events:
1 class event về cơ bản là 1 data container chứa thông tin liên quan đến event. Ví dụ, event App\Events\OrderShipped nhận 1 Eloquent ORM:
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderShipped
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(
public Order $order,
) {}
}Class event này không có code logic. Nó chỉ chứa 1 instance Order. Trait SerializesModels sử dụng bởi event sẽ serialize bất kỳ Eloquent models nào nếu đối tượng event được serialize sử dụng hàm serialize của PHP, chẳng hạn khi sử dụng queued listeners.
Tạo Listeners:
Listeners nhận event instance trong method handle. Lệnh event:generate và make:listener sẽ tự động import event class thích hợp và type-hint event vào method handle. Trong method này, bạn có thể tạo bất kỳ hành động cần thiết để phản hồi event:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
class SendShipmentNotification
{
/**
* Create the event listener.
*/
public function __construct()
{
// ...
}
/**
* Handle the event.
*/
public function handle(OrderShipped $event): void
{
// Access the order using $event->order...
}
}Event listeners cũng có thể type-hint dependency cần thiết trong constructor. Tất cả event listeners được resolve qua service container nên sẽ được inject tự động.
Dừng 1 sự kiện:
Thi thoảng bạn cần dừng sự kiện với các listener khác bằng cách return false trong method handle.
Queued Event Listeners:
Xếp listener vào hàng đợi giúp ứng dụng vẫn hoạt động bình thường khi tác vụ listener chậm, ví dụ như gửi mail. Cần cấu hình queue (xem bài Queued Laravel).
Để chỉ định listener nào được queued, thêm interface ShouldQueue vào class listener:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendShipmentNotification implements ShouldQueue
{
// ...
}Khi listener được kích hoạt, nó sẽ được xếp vào queue của Laravel. Nếu không có ngoại lệ được ném ra, queue job sẽ tự động xóa sau khi kết thúc.
Xử lý Failed Jobs:
Thi thoảng queued listener có thể fail. Nếu queued listener thực hiện quá số lần đã được define bởi queue worker, method failed sẽ được gọi. Method này nhận event instance và Throwable đã gây ra fail:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Throwable;
class SendShipmentNotification implements ShouldQueue
{
use InteractsWithQueue;
/**
* Handle the event.
*/
public function handle(OrderShipped $event): void
{
// ...
}
/**
* Handle a job failure.
*/
public function failed(OrderShipped $event, Throwable $exception): void
{
// ...
}
}Chỉnh định số lần thử của queued listener:
Thuộc tính $tries trong listener:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class SendShipmentNotification implements ShouldQueue
{
use InteractsWithQueue;
/**
* The number of times the queued listener may be attempted.
*
* @var int
*/
public $tries = 5;
}Hoặc sử dụng DateTime với method retryUntil:
use DateTime;
/**
* Determine the time at which the listener should timeout.
*/
public function retryUntil(): DateTime
{
return now()->addMinutes(5);
}Kích hoạt Event:
Sử dụng method dispatch, method này có sẵn trong trait Illuminate\Foundation\Events\Dispatchable. Các tham số được pass vào method dispatch sẽ được pass đến event constructor:
<?php
namespace App\Http\Controllers;
use App\Events\OrderShipped;
use App\Http\Controllers\Controller;
use App\Models\Order;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class OrderShipmentController extends Controller
{
/**
* Ship the given order.
*/
public function store(Request $request): RedirectResponse
{
$order = Order::findOrFail($request->order_id);
// Order shipment logic...
OrderShipped::dispatch($order);
return redirect('/orders');
}
}Kích hoạt event sử dụng điều kiện:
OrderShipped::dispatchIf($condition, $order);
OrderShipped::dispatchUnless($condition, $order);Event Subscribers:
Event Subscribers là những class có thể subscribe đến nhiều event từ trong chính nó, cho phép bạn define nhiều event handlers trong 1 class. Define method subscribe, method này nhận tham số là 1 event dispatcher instance. Bạn có thể gọi listen method trên dispatcher đã cho để đăng ký listener:
<?php
namespace App\Listeners;
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Events\Dispatcher;
class UserEventSubscriber
{
/**
* Handle user login events.
*/
public function handleUserLogin(Login $event): void {}
/**
* Handle user logout events.
*/
public function handleUserLogout(Logout $event): void {}
/**
* Register the listeners for the subscriber.
*/
public function subscribe(Dispatcher $events): void
{
$events->listen(
Login::class,
[UserEventSubscriber::class, 'handleUserLogin']
);
$events->listen(
Logout::class,
[UserEventSubscriber::class, 'handleUserLogout']
);
}
}Nếu method của listener được define trong chính subscriber, bạn có thể return 1 mảng event và tên method. Laravel sẽ tự động xác định và đăng ký listener:
<?php
namespace App\Listeners;
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Events\Dispatcher;
class UserEventSubscriber
{
/**
* Handle user login events.
*/
public function handleUserLogin(Login $event): void {}
/**
* Handle user logout events.
*/
public function handleUserLogout(Logout $event): void {}
/**
* Register the listeners for the subscriber.
*
* @return array<string, string>
*/
public function subscribe(Dispatcher $events): array
{
return [
Login::class => 'handleUserLogin',
Logout::class => 'handleUserLogout',
];
}
}Đăng ký Event Subscribers:
Sau khi viết subscriber, cần đăng ký nó với event dispatcher. Sử dụng thuộc tính $subscribe của EventServiceProvider. Ví dụ:
<?php
namespace App\Providers;
use App\Listeners\UserEventSubscriber;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
// ...
];
/**
* The subscriber classes to register.
*
* @var array
*/
protected $subscribe = [
UserEventSubscriber::class,
];
}