[Laravel] Event vs Notification
Có hai khái niệm mọi người hay lúng túng khi sử dụng, đó là sự kiện (Event) và thông báo (Notification). Hai khái niệm này khá tương đồng, nó được sử dụng để quảng bá (broadcast) một thông điệp gì đó từ server đến cho người dùng. Đôi khi trong ứng dụng của bạn, bạn có thể áp dụng cái này thay cho cái kia, và tất nhiên ứng dụng vẫn hoạt động bình thường. Tuy nhiên để cho đúng ngữ nghĩa, tôi xin so sánh qua về sự khác nhau của 2 khái niệm này và các áp dụng nó trong Laravel.
Sự kiện (Event)
Sự kiện theo mình hiểu là một điều gì đó xảy ra trong hệ thống và cần có các phản ứng lại (lắng nghe) sự kiện đó. Khái niệm này không chỉ với Laravel, nó rộng hơn là với cả JavaScript, bên JS có lẽ còn sử dụng nhiều hơn, các listener thường được truyền dưới dạng callback. Trong cuộc sống hằng ngày chúng ta cũng có rất nhiều sự kiện: ví dụ như khi bạn mua hàng, bạn đã đặt hàng xong, đó sẽ là 1 sự kiện với cửa hàng, họ cần lắng nghe sự kiện này sau đó thực hiện các nhiệm vụ ghi hóa đơn, gửi thông báo mua hàng cho bạn qua email, thực hiện thanh toán.
Như vậy rất rõ ràng, 1 sự kiện sẽ bao gồm 2 phần chính: Đăng ký sự kiện và Lắng nghe - khi sự kiện xảy ra thì xử lý như thế nào.
Laravel sử dụng sự kiện như thế nào
Đầu tiên để đăng kí sự kiện trong Laravel, bạn sử dụng lệnh artisan
để tạo:1
php artisan make:event EventName
Bạn có để ý 1 chú ý nhỏ trên docs và các bài hướng dẫn Laravel, EventName thường sẽ ở dạng quá khứ, ví dụ như: sản phẩm đã được mua, bài viết đã được click, người dùng đã đăng kí tài khoản …Do đó đây cũng là lưu ý nhỏ khi bạn đặt tên các sự kiện. Một vài ví dụ về tên sự kiện bạn có thể gặp:
ProductPurchased
OrderShipped
UserRegistered
Khi đã đăng kí sự kiện rồi, công việc tiếp theo là đăng kí Listenner để xử lý khi sự kiện xảy ra:1
php artisan make:listener ListenerName
Lại một chú ý nữa, tên của listener thì vẫn như các phương thức bình thường =)). Một vài tên listener bạn có thể tham khảo:
SendEmailVerificationNotification
AwardAchievements
SendShipmentNotification
Tiếp theo chúng ta cần đăng kí event với listener.
Như vậy chúng ta đã có Event và Listener, tuy nhiên chúng đang tồn tại độc lập mà không có sự liên kết. Để liên kết chúng lại với nhau, chúng ta sẽ đăng kí trong file App\Providers\EventServiceProvider
. Một sự kiện có thể được đăng kí nhiều listener là một listener cũng có thể thuộc nhiều event, nhưng trường hợp này có lẽ hiếm: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
namespace App\Providers;
use App\Events\ProductPurchased;
use App\Listeners\AwardAchievements;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
ProductPurchased::class => [
AwardAchievements::class,
],
];
/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
parent::boot();
//
}
}
Một vài thông tin khác về sự kiện trong Laravel
1. Sự kiện này có thể được đăng kí bằng cách khác.
Bạn đăng kí tên event, listener class vào luôn file EventServiceProvider
rồi sau đó sinh sự kiện bằng:1
php artisan event:generate
Hãy sử dụng cả namespace file đó để tạo file đúng như dự kiến nhé.1
2use App\Events\ProductPurchased;
use App\Listeners\AwardAchievements;
Vì nếu không có use này nó sẽ tạo luôn ở thư mục với EventServiceProvider
.
2. Laravel có thể tự tìm kiếm được sự kiện và listener
Bạn không cần đăng kí vào $listen
trong file EventServiceProvider
(không khuyến khích cách này vì khó theo dõi sự kiện)
Để làm việc này, bạn hãy sử dụng thêm phương thức trong EventServiceProvider
1
2
3
4
5
6
7
8
9/**
* Determine if events and listeners should be automatically discovered.
*
* @return bool
*/
public function shouldDiscoverEvents()
{
return true;
}
3. Các Event trong Laravel hoàn toàn có thể đẩy vào hàng đợi giúp tăng tốc độ xử lý (ví dụ như thao tác gửi mail có thể mất nhiều thời gian)
4. Dispatching Events
Để phát ra sự kiện, có thể sử dụng phương thức dispatch()
của event. Tuy nhiên để cú pháp đơn giản, bạn sử dụng luôn helper event cho tiện :D1
2ProductPurchased::dispatch('toy');
event(new ProductPurchased('toy'));
5. Xem danh sách các sự kiện và listener tương ứng1
php aritsan event:list
Ngoài ra còn có các lệnh sau liên quan đến event:1
2
3
4
5event
event:cache Discover and cache the application's events and listeners
event:clear Clear all cached events and listeners
event:generate Generate the missing events and listeners based on registration
event:list List the application's events and listeners
6. Event Subscribers
Event Subscribers sử dụng để theo dõi các sự kiện trong chính các class đó, cho phép bạn định nghĩa các event handlers trong 1 lớp đơn. Phần này bạn hiểu là không cần sử dụng nhiều listener cho mỗi sự kiện mà sử dụng nó thành 1 lớp đơn …
- Tạo Event Subscribers
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
namespace App\Listeners;
class OrderEventSubscriber
{
/**
* Handle user buy a product.
*/
public function handleBuyProduct($event) {}
/**
* Handle order are shipped.
*/
public function handleSendOrderEmail($event) {}
/**
* Register the listeners for the subscriber.
*
* @param \Illuminate\Events\Dispatcher $events
*/
public function subscribe($events)
{
$events->listen(
'App\Events\ProductPurchased',
'App\Listeners\UserEventSubscriber@handleBuyProduct'
);
$events->listen(
'App\Events\OrderShipped',
'App\Listeners\UserEventSubscriber@handleSendOrderEmail'
);
}
} - Đăng kí Event Subscribers
Sử dụng biến $subscribers
nhé: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
namespace App\Providers;
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 = [
'App\Listeners\OrderEventSubscriber',
];
}
Phần này được sử dụng khi mà các tập sự kiện có liên quan đến nhau, như vậy sẽ tiện theo dõi hơn. Như ví dụ ở trên, người ta nhóm các tập sự kiện liên quan đến đơn hàng vào 1 listener duy nhất :D
Tổng kết về sự kiện
- Như vậy 1 sự kiện có 2 phần: Đăng kí event (thư mục
App\Events
) và Xử lý sự kiện dưới dạng Listener (App\Listeners
). - Sự kiện có thể đẩy vào hàng đợi.
- Laravel có cung cấp chức năng discover events.
- Khi có 1 tập sự kiện liên quan mà không muốn chia nhỏ Listener (Ví dụ như authentication, xử lý đơn hàng), bạn có thể nhóm chngs lại với Event Subscribers tạo 1 listener duy nhất).
Notification
Hãy trở lại bài toán mua hàng ở bên trên. Khi mà một người dùng mua hàng, chúng ta tạo ra một thông báo. Thông báo này có nhiệm vụ gửi một tin nhắn đến cho người dùng, tin nhắn này có thể là SMS/Slack-messgae/Email hoặc một thông báo lên web như facebook. Chúng ta chọn thông báo vì chúng ta muốn gửi một “message” giống nhau thông qua nhiều channel.
Khoan hãy so sánh về sự kiện và thông báo, hãy cùng xem Laravel thực hiện nó như thế nào
Tạo một thông báo
Để tạo 1 thông báo trong Laravel, khá đơn giản, bạn sử dụng lệnh artisan
1 | php artisan make:notification InvoicePaid |
Mặc định, class Notification này sẽ có 1 phương thức cho 2 channel để gửi thông báo là email và database. Về cách sử dụng của các channel này mình sẽ đề cập trong phần sau
Gửi thông báo
Thông báo có thể gửi bằng 2 cách: sử dụng phương thức notify của trait Notifiable
hoặc sử dụng Notification
facade:1
2
3use App\Notifications\InvoicePaid;
$user->notify(new InvoiceaymentPaid($invoice));
Hoặc1
Notification::send(request()->user(), new InvoiceaymentPaid());
Một vài thông tin khác về notification trong Laravel
1. Bạn có thể chọn kênh gửi, một hoặc nhiều
Nó phụ thuộc vào biến $notification
thông qua phương thức via
. Có thể thêm điều kiện nha :D1
2
3
4
5
6
7
8
9
10/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return $notifiable->prefers_sms ? ['nexmo'] : ['mail', 'database'];
}
2. Queueing Notifications
Cũng giống như sự kiện, bạn hoàn toàn có thể đặt thông báo của mình vào queue để tăng tốc độ xử lý
3. Gửi thông báo theo yêu cầu
Đôi khi bạn cần gửi thông báo tới 1 vài người không được lưu trữ như là 1 “user” trong ứng dụng của bạn. Hãy sử dụng phương thức Notification::route, bạn có thể chỉ định được người nhận thông báo:1
2
3Notification::route('mail', 'taylor@example.com')
->route('nexmo', '5555555555')
->notify(new InvoicePaid($invoice));
Kênh chuyển tiếp thông tin
Laravel cung cấp cho bạn rất nhiều các channel cho phép bạn chuyển tiếp thông báo tới người dùng. Trong phạm vi bài viết, mình giới thiệu 2 kênh cơ bản là Database và Email, các channel.
Tất cả các channel để gửi thông báo bạn chỉ cần thực hiện 2 việc: đầu tiên, khai báo channel bạn muốn sử dụng đó vào phương thức via
và sau đó sử dụng phương thức tương ứng trong class Notification để xử lý các gửi mail hoặc nội dung thông báo.
Mail Notifications
Để sử dụng gửi thông báo qua email, bạn đăng kí nó trong via
trước (cái này mặc định laravel đã sử dụng)1
2
3
4public function via($notifiable)
{
return ['mail'];
}
Sau đó thực hiện gửi mai như thế nào, gửi tới ai và nội dung gì, bạn hoàn toàn thực hiện trong phương thức toMail của lớp Notification:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
$url = url('/invoice/'.$this->invoice->id);
return (new MailMessage)
->greeting('Hello!')
->line('One of your invoices has been paid!')
->action('View Invoice', $url)
->line('Thank you for using our application!');
}
Bạn hoàn toàn có thể tùy biến việc gửi mail này như chọn template, xử lý lỗi, chọn người gửi, người nhận, xử lý queue. Chi tiết có thể xem tại trang chủ.
Database Notifications
Phần này mình cũng thấy khá hay. Database channel sẽ lưu trữ thông tin thông báo vào database. Bảng này sẽ chưa các thông tin như loại thông báo cũng như dữ liệu dạng JSON tùy chỉnh mô tả cho thông báo.
Đầu tiên bạn tạo bảng và migrate dữ liệu1
2
3php artisan notifications:table
php artisan migrate
Bảng notification sẽ có những trường cơ bản sau:
- id: khóa
- type: Tham chiếu tới
Notification
class. Bạn có thể check type này để hiển thị thông báo phù hợp. Như ví dụ trên trường này sẽ làApp\Notifications\PaymentReceived
- notifiable_type: Class nào phát ra thông báo. Ví dụ thường là user:
App\User
- notifiable_id: id của notifiable_type
- data: dữ liệu dạng JSON về các thông tin của thông báo. Ví dụ Từ data này bạn có thể tạo ra các nội dung thông báo phù hợp.
1
2
3
4{
"amount": 90,
"customer_name": "Ming"
} - read_at: trường này để xác định thông báo đã được đọc lúc nào, tất nhiên mặc định nó sẽ là null. Các thông báo chưa đọc hay đã đọc sẽ được load dựa vào trường này.
- created_at, updated_at: lưu tời gian và update thông báo
Đăng kí channel trong phương thức via
1 | public function via($notifiable) |
Format thông báo dựa vào 2 phương thức toDatabase
và toArray
. Các phương thức này sẽ nhận vào $notifiable và sẽ trả về dạng mảng. Kết quả mảng trả về sẽ được encoded như JSON được lưu trữ trong cột data của bảng notifications
:1
2
3
4
5
6
7
8
9
10
11
12
13/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
'invoice_id' => $this->invoice->id,
'amount' => $this->invoice->amount,
];
}
toDatabase Vs. toArray
The
toArray
method is also used by the broadcast channel to determine which data to broadcast to your JavaScript client. If you would like to have two different array representations for the database and broadcast channels, you should define a toDatabase method instead of atoArray
method.
Lấy dữ liệu thông báo
Một vài phương thức lấy dữ liệu thông báo:
Lấy hết1
2
3
4
5$user = App\User::find(1);
foreach ($user->notifications as $notification) {
echo $notification->type;
}
Lấy các thông báo chưa đọc1
2
3
4
5$user = App\User::find(1);
foreach ($user->unreadNotifications as $notification) {
echo $notification->type;
}
Đánh dấu đã đọc thông báo1
2
3
4
5$user = App\User::find(1);
foreach ($user->unreadNotifications as $notification) {
$notification->markAsRead();
}
Hoặc1
$user->unreadNotifications->markAsRead();
Xóa thông báo1
$user->notifications()->delete();
Ngoài ra Laravel còn cung cấp các channel khác giúp bạn gửi thông báo tiện dụng
- SMS Notifications: Gửi thông báo qua tin nhắn SMS.
Bạn có thể tham khảo thêm tại:
https://laravel.com/docs/5.8/notifications#sending-notifications
và
https://laracasts.com/series/laravel-6-from-scratch/episodes/48
- Slack Notifications: Gửi thông báo qua hệ thống chat Slack.
- Broadcast Notifications: Quảng bá thông báo với Pusher và Laravel Echo.
Tổng kết về thông báo
- Thông báo có thể được xem là cách thức gửi 1 thông điệp đến người dùng bằng nhiều kênh khác nhau. Hoặc nó cũng là gửi 1 thông điệp giống nhau đến nhiều người dùng khác nhau.
- Chọn kênh gửi qua phương thức
via
, format nội dung và thực hiện gửi thông qua các phương thứctoArray
,toMail
,… tương ứng - Thông báo hoàn toàn có thể đặt vào hàng đợi để tránh block ứng dụng.
- Laravel cung cấp đầy đủ các hàm cho thông báo: lấy tất cả thông báo, lấy các thông báo đã đọc, chưa đọc, hay các hàm đọc thông báo, đánh dấu chưa đọc, xóa :D
Tản mạn về sự kiện vs thông báo
Chúng ta có thể thấy rằng, khi đặt xong 1 đơn hàng, server cần gửi 1 email về thông tin đơn hàng đến cho client, và lúc này chúng ta dùng event hay notification để thực hiện đều được. Tuy nhiên để cho đúng ngữ nghĩa, tôi nghĩ chúng ta nên sử dụng thông báo.
Sự kiện nên được sử dụng khi chúng ta cần làm 1 số việc gì đó khi điều kiện cho trước xảy ra và các công việc phản ứng lại sự kiện này PHẢI được thực hiện trong luồng làm việc, ví dụ
- Sự kiện gửi email kích hoạt khi người dùng đăng kí
- Sự kiện cập nhật lượng view bài viết khi 1 bài viết được click vào …
(trong các ví dụ trên, sự kiện mà không thực hiện thì sai logic). Sự kiện thường là các thao tác database, đôi khi là gửi email
Thông báo thì khác, nó nên được sử dụng để ghi lại cái đã xảy ra, ví dụ như:
- 1 ai đó đã thích ảnh của bạn => ghi lại hành động này thông báo tới client,
- 1 hàng động đặt hàng của bạn vừa hoàn tất => ghi lại việc đặt hàng và gửi tới cho client qua email, SMS, không cần user xác nhận gì mà chỉ xem lại nếu muốn =))
Như vậy thông báo chỉ là bổ sung chức năng cho ứng dụng, không cần phản hồi từ người dùng. Thông báo là 1 cách tốt để gửi 1 thông điệp qua nhiều kênh hoặc gửi 1 thông điệp tới nhiều người dùng khác nhau.
Cái này chỉ là tự bản thân mình thấy thôi, tùy vào ứng dụng mà bạn chọn event hay notification cho hợp lý nhé. Tất nhiên cũng đừng sử dụng event kiểu như sau khi đơn hàng được tạo thì tạo chuỗi event: gửi email tới client, gửi SMS tới client thì cũng hơi kì :D
Tài liệu tham khảo
[Laravel] Event vs Notification