通知 - Notification

簡介

除了支援寄送郵件外,Laravel 還支援以各種不同的通道來寄送通知。支援的通道包含電子郵件、簡訊 (使用 Vonage,,前名 Nexmo)、Slack 等。此外,還有許多社群製作的通知通道,可使用數十種不同的通道來傳送通知!通知也可以儲存在資料庫中以在網頁界面上顯示。

一般來說,通知是簡短的、資訊性的訊息,用來通知使用者我們的程式裡發生的事情。舉例來說,假設我們正在開發一款請款用的程式,我們可以使用電子郵件與簡訊管道來傳送一個「已收到款項」的通知。

產生通知

在 Laravel 中,通知以 app/Notifications 目錄中的一個類別的形式來呈現。若在專案中沒看到這個目錄,請別擔心 —— 執行 make:notification 後就會自動建立該目錄:

1php artisan make:notification InvoicePaid
1php artisan make:notification InvoicePaid

這個指令會在 app/Notifications 目錄下建立一個新的 Notification。每個 Notification 中都包含了一個 via 方法與不定數量的訊息建立方法,如 toMailtoDatabase,這些訊息建立方法將通知轉換為特定頻道格式的訊息。

傳送通知

使用 Notifiable Trait

有兩種方法可以傳送通知:使用 Notifiable Trait 的 notify 方法,或是使用 Notification FacadeNotifiable Trait 已預設包含在專案的 App\Models\User 中:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Foundation\Auth\User as Authenticatable;
6use Illuminate\Notifications\Notifiable;
7 
8class User extends Authenticatable
9{
10 use Notifiable;
11}
1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Foundation\Auth\User as Authenticatable;
6use Illuminate\Notifications\Notifiable;
7 
8class User extends Authenticatable
9{
10 use Notifiable;
11}

該 Trait 提供的 notify 方法預期接收一個通知實體:

1use App\Notifications\InvoicePaid;
2 
3$user->notify(new InvoicePaid($invoice));
1use App\Notifications\InvoicePaid;
2 
3$user->notify(new InvoicePaid($invoice));
lightbulb

請記得,任何的 Model 都可以使用 Notifiable Trait。不是只有 User Model 上才能用。

使用 Notification Facade

或者,也可以使用 Nofication [Facade] 來傳送通知。若要傳送通知給多個 Notifiable 實體 (如一組 User Collection),就很適合這種方法。若要使用該 Facade 傳送通知,請將所有 Notifiable 實體與 Notification 實體傳給 send 方法:

1use Illuminate\Support\Facades\Notification;
2 
3Notification::send($users, new InvoicePaid($invoice));
1use Illuminate\Support\Facades\Notification;
2 
3Notification::send($users, new InvoicePaid($invoice));

也可以使用 sendNow 方法來馬上傳送通知。即使通知有實作 ShouldQueue 介面,該方法也會立即傳送通知:

1Notification::sendNow($developers, new DeploymentCompleted($deployment));
1Notification::sendNow($developers, new DeploymentCompleted($deployment));

指定傳送通道

每個 Notification 類別都有一個 via 方法,用來判斷該通知要在哪些通道上傳送。通知在 maildatabasebroadcastvonageslack等通道上傳送:

lightbulb

若想使用其他通道傳送,如 Telegram 或 Pusher,請參考看看由社群提供的 Laravel Notification Channels 網站

via 方法會收到一個 $notifiable 實體,也就是該通知正在傳給的類別實體。可使用 $nofiable 來判斷該通知要在哪些通道上傳送:

1/**
2 * Get the notification's delivery channels.
3 *
4 * @return array<int, string>
5 */
6public function via(object $notifiable): array
7{
8 return $notifiable->prefers_sms ? ['vonage'] : ['mail', 'database'];
9}
1/**
2 * Get the notification's delivery channels.
3 *
4 * @return array<int, string>
5 */
6public function via(object $notifiable): array
7{
8 return $notifiable->prefers_sms ? ['vonage'] : ['mail', 'database'];
9}

將通知放入佇列

exclamation

在將通知放入佇列前,請先設定好佇列,並執行一個 Worker(背景工作角色)

傳送通知可能會需要花費時間,特別是需要使用外部 API 呼叫來傳送通知的頻道。若要加速程式的回應時間,可在通知類別上加入 ShouldQueue 介面與 Queueable Trait 來讓通知使用佇列。使用 make:notification 指令產生的通知中,預設已有匯入該介面與 Trait,因此我們可以直接將其加入通知類別:

1<?php
2 
3namespace App\Notifications;
4 
5use Illuminate\Bus\Queueable;
6use Illuminate\Contracts\Queue\ShouldQueue;
7use Illuminate\Notifications\Notification;
8 
9class InvoicePaid extends Notification implements ShouldQueue
10{
11 use Queueable;
12 
13 // ...
14}
1<?php
2 
3namespace App\Notifications;
4 
5use Illuminate\Bus\Queueable;
6use Illuminate\Contracts\Queue\ShouldQueue;
7use Illuminate\Notifications\Notification;
8 
9class InvoicePaid extends Notification implements ShouldQueue
10{
11 use Queueable;
12 
13 // ...
14}

ShouldQueue 介面加入通知後,可像平常一樣傳送通知。Laravel 會在偵測到該類別有 ShouldQueue 介面後自動以佇列寄送通知:

1$user->notify(new InvoicePaid($invoice));
1$user->notify(new InvoicePaid($invoice));

在將通知放入佇列時,Laravel 會為每個收件人與每個通道的組合建立佇列任務(Job)。舉例來說,若通知有三個收件人與兩個通道,則會派發(Dispatch)六個任務。

延遲通知

若想延遲傳送通知,可在通知初始化之後串聯呼叫 delay 方法:

1$delay = now()->addMinutes(10);
2 
3$user->notify((new InvoicePaid($invoice))->delay($delay));
1$delay = now()->addMinutes(10);
2 
3$user->notify((new InvoicePaid($invoice))->delay($delay));

依照通道延遲通知

可傳入陣列給 delay 方法來指定特定通道要延遲的時間:

1$user->notify((new InvoicePaid($invoice))->delay([
2 'mail' => now()->addMinutes(5),
3 'sms' => now()->addMinutes(10),
4]));
1$user->notify((new InvoicePaid($invoice))->delay([
2 'mail' => now()->addMinutes(5),
3 'sms' => now()->addMinutes(10),
4]));

或者,也可以在通知類別內定義 withDelay 方法。withDelay 方法應回傳一組通道名稱的陣列,以及延遲值:

1/**
2 * Determine the notification's delivery delay.
3 *
4 * @return array<string, \Illuminate\Support\Carbon>
5 */
6public function withDelay(object $notifiable): array
7{
8 return [
9 'mail' => now()->addMinutes(5),
10 'sms' => now()->addMinutes(10),
11 ];
12}
1/**
2 * Determine the notification's delivery delay.
3 *
4 * @return array<string, \Illuminate\Support\Carbon>
5 */
6public function withDelay(object $notifiable): array
7{
8 return [
9 'mail' => now()->addMinutes(5),
10 'sms' => now()->addMinutes(10),
11 ];
12}

自訂通知的佇列連線

預設情況下,佇列通知會使用專案的預設佇列連線。若想為特定通知指定不同的連線,可在 Notification 類別上定義一個 $connection 屬性:

1/**
2 * The name of the queue connection to use when queueing the notification.
3 *
4 * @var string
5 */
6public $connection = 'redis';
1/**
2 * The name of the queue connection to use when queueing the notification.
3 *
4 * @var string
5 */
6public $connection = 'redis';

或者,若想為通知所支援的各個通知通道個別指定佇列連線,可在通知上定義一個 viaConnections 方法。這個方法應回傳一組通道名稱 / 佇列名稱配對的陣列:

1/**
2 * Determine which connections should be used for each notification channel.
3 *
4 * @return array<string, string>
5 */
6public function viaConnections(): array
7{
8 return [
9 'mail' => 'redis',
10 'database' => 'sync',
11 ];
12}
1/**
2 * Determine which connections should be used for each notification channel.
3 *
4 * @return array<string, string>
5 */
6public function viaConnections(): array
7{
8 return [
9 'mail' => 'redis',
10 'database' => 'sync',
11 ];
12}

自訂通知通道佇列

若想為某個通知所支援的各個通知通道指定特定的佇列,可在通知上定義一個 viaQueues 方法。這個方法應會傳一組通知名稱 / 佇列名稱配對的陣列:

1/**
2 * Determine which queues should be used for each notification channel.
3 *
4 * @return array<string, string>
5 */
6public function viaQueues(): array
7{
8 return [
9 'mail' => 'mail-queue',
10 'slack' => 'slack-queue',
11 ];
12}
1/**
2 * Determine which queues should be used for each notification channel.
3 *
4 * @return array<string, string>
5 */
6public function viaQueues(): array
7{
8 return [
9 'mail' => 'mail-queue',
10 'slack' => 'slack-queue',
11 ];
12}

佇列的通知與資料庫 Transaction

當佇列通知是在資料庫 Transaction 內分派(Dispatch)的時候,這個通知可能會在資料庫 Transaction 被 Commit 前就被佇列進行處理了。發生這種情況時,在資料庫 Transaction 期間對 Model 或資料庫記錄所做出的更新可能都還未反應到資料庫內。另外,所有在 Transaction 期間新增的 Model 或資料庫記錄也可能還未出現在資料庫內。若該通知有使用這些 Model 的話,處理該通知的佇列任務時可能會出現未預期的錯誤。

若佇列的 after_commit 選項設為 false,則我們還是可以通過在傳送通知前呼叫 afterCommit 方法來標示出該 Mailable 應在所有資料庫 Transaction 都被 Commit 後才分派:

1use App\Notifications\InvoicePaid;
2 
3$user->notify((new InvoicePaid($invoice))->afterCommit());
1use App\Notifications\InvoicePaid;
2 
3$user->notify((new InvoicePaid($invoice))->afterCommit());

或者,也可以在 Notification 的 Constructor 上呼叫 afterCommit 方法:

1<?php
2 
3namespace App\Notifications;
4 
5use Illuminate\Bus\Queueable;
6use Illuminate\Contracts\Queue\ShouldQueue;
7use Illuminate\Notifications\Notification;
8 
9class InvoicePaid extends Notification implements ShouldQueue
10{
11 use Queueable;
12 
13 /**
14 * Create a new notification instance.
15 */
16 public function __construct()
17 {
18 $this->afterCommit();
19 }
20}
1<?php
2 
3namespace App\Notifications;
4 
5use Illuminate\Bus\Queueable;
6use Illuminate\Contracts\Queue\ShouldQueue;
7use Illuminate\Notifications\Notification;
8 
9class InvoicePaid extends Notification implements ShouldQueue
10{
11 use Queueable;
12 
13 /**
14 * Create a new notification instance.
15 */
16 public function __construct()
17 {
18 $this->afterCommit();
19 }
20}
lightbulb

要瞭解更多有關這類問題的解決方法,請參考有關佇列任務與資料庫 Transaction 有關的說明文件。

判斷是否應送出某個佇列的通知

當佇列通知被派發出去給背景執行後,通常會被傳給佇列 Worker 並進一步傳送給指定的收件人。

不過,在通知要被佇列 Worker 處理時,若我們還想就是否應傳送該佇列通知作最後的決定,可在該 Notification 類別上定義一個 shouldSend 方法。若該方法回傳 false,就不會傳送這個通知:

1/**
2 * Determine if the notification should be sent.
3 */
4public function shouldSend(object $notifiable, string $channel): bool
5{
6 return $this->invoice->isPaid();
7}
1/**
2 * Determine if the notification should be sent.
3 */
4public function shouldSend(object $notifiable, string $channel): bool
5{
6 return $this->invoice->isPaid();
7}

隨需通知

有時候,我們會需要將通知傳給不是我們網站使用者的人。只要使用 Notification Facade 上的 route 方法,就可以在送出通知前指定特別的通知 Route 資訊:

1use Illuminate\Broadcasting\Channel;
2 
3Notification::route('mail', '[email protected]')
4 ->route('vonage', '5555555555')
5 ->route('slack', 'https://hooks.slack.com/services/...')
6 ->route('broadcast', [new Channel('channel-name')])
7 ->notify(new InvoicePaid($invoice));
1use Illuminate\Broadcasting\Channel;
2 
3Notification::route('mail', '[email protected]')
4 ->route('vonage', '5555555555')
5 ->route('slack', 'https://hooks.slack.com/services/...')
6 ->route('broadcast', [new Channel('channel-name')])
7 ->notify(new InvoicePaid($invoice));

若想在傳送隨需通知時為 mail Route 提供收件人名稱,可提供一個索引鍵為郵件位址而值為姓名的陣列:

1Notification::route('mail', [
2 '[email protected]' => 'Barrett Blair',
3])->notify(new InvoicePaid($invoice));
1Notification::route('mail', [
2 '[email protected]' => 'Barrett Blair',
3])->notify(new InvoicePaid($invoice));

郵件通知

格式化郵件通知

若要支援以電子郵件傳送通知,請在該通知類別上定義一個 toMail 方法。這個方法會收到一個 $notifiable 實體,而回傳值應為 Illuminate\Notifications\Messages\MailMessage 實體。

MailMessage 類別包含一些簡單的方法,可以讓我們快速建立交易(Transactional)電子郵件訊息。郵件訊息可包含數行的文字,以及「執行動作」。來看看一個範例的 toMail 方法:

1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 $url = url('/invoice/'.$this->invoice->id);
7 
8 return (new MailMessage)
9 ->greeting('Hello!')
10 ->line('One of your invoices has been paid!')
11 ->lineIf($this->amount > 0, "Amount paid: {$this->amount}")
12 ->action('View Invoice', $url)
13 ->line('Thank you for using our application!');
14}
1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 $url = url('/invoice/'.$this->invoice->id);
7 
8 return (new MailMessage)
9 ->greeting('Hello!')
10 ->line('One of your invoices has been paid!')
11 ->lineIf($this->amount > 0, "Amount paid: {$this->amount}")
12 ->action('View Invoice', $url)
13 ->line('Thank you for using our application!');
14}
lightbulb

請注意,在 toMail 中,我們使用了 $this->invoice->id。我們可以將通知訊息所需要的任何資料傳入該通知的 Constructor(建構函式) 中。

在這個範例中,我們註冊了一個招呼語(Greeting)一行文字(Line),一個動作(Action),然後是又一行的文字(Line)MailMessage 物件提供的這些方法讓我們可以簡單快速地格式化簡短的交易電子郵件。Mail 通道會將該這些訊息元件翻譯為漂亮的回應式 HTML 電子郵件樣板與一個回應的純文字版本。下列是 mail 通道產生的電子郵件範例:

lightbulb

在傳送郵件通知時,請確保有在 config/app.php 設定檔中設定 name 設定選項。在郵件通知訊息的頁頭與頁尾中會使用到這個值。

錯誤訊息

有些通知是用來通知使用者錯誤的,如付款失敗等。可在建立訊息時呼叫 error 方法來標示該郵件訊息是錯誤通知。在郵件訊息上使用 error 方法時,動作按鈕會從黑色變成紅色的:

1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->error()
8 ->subject('Invoice Payment Failed')
9 ->line('...');
10}
1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->error()
8 ->subject('Invoice Payment Failed')
9 ->line('...');
10}

其他郵件通知的格式化選項

除了在 Notification 類別中定義「line」以外,也可以使用 view 方法來指定用來轉譯通知郵件的自訂樣板:

1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)->view(
7 'emails.name', ['invoice' => $this->invoice]
8 );
9}
1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)->view(
7 'emails.name', ['invoice' => $this->invoice]
8 );
9}

可以傳入一個陣列給 view 方法,並在該陣列的第二個元素上指定純文字版本的 View 名稱,以為郵件訊息指定純文字版本:

1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)->view(
7 ['emails.name.html', 'emails.name.plain'],
8 ['invoice' => $this->invoice]
9 );
10}
1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)->view(
7 ['emails.name.html', 'emails.name.plain'],
8 ['invoice' => $this->invoice]
9 );
10}

自訂寄件人

預設情況下,郵件的寄送人 / 寄件位址是在 config/mail.php 設定檔中定義的。不過,我們也可以使用 from 方法來為特定的通知指定寄件位址:

1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->from('[email protected]', 'Barrett Blair')
8 ->line('...');
9}
1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->from('[email protected]', 'Barrett Blair')
8 ->line('...');
9}

自訂收件人

使用 mail 通道傳送通知時,通知系統會自動在 Notifiable 實體上尋找 email 屬性。可在該 Notifiable 實體上定義一個 routeNotificationForMail 方法來自訂通知要傳送給哪個電子郵件位址:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Foundation\Auth\User as Authenticatable;
6use Illuminate\Notifications\Notifiable;
7use Illuminate\Notifications\Notification;
8 
9class User extends Authenticatable
10{
11 use Notifiable;
12 
13 /**
14 * Route notifications for the mail channel.
15 *
16 * @return array<string, string>|string
17 */
18 public function routeNotificationForMail(Notification $notification): array|string
19 {
20 // 只回傳 E-Mail 位址...
21 return $this->email_address;
22 
23 // 回傳 E-Mail 位址與名稱...
24 return [$this->email_address => $this->name];
25 }
26}
1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Foundation\Auth\User as Authenticatable;
6use Illuminate\Notifications\Notifiable;
7use Illuminate\Notifications\Notification;
8 
9class User extends Authenticatable
10{
11 use Notifiable;
12 
13 /**
14 * Route notifications for the mail channel.
15 *
16 * @return array<string, string>|string
17 */
18 public function routeNotificationForMail(Notification $notification): array|string
19 {
20 // 只回傳 E-Mail 位址...
21 return $this->email_address;
22 
23 // 回傳 E-Mail 位址與名稱...
24 return [$this->email_address => $this->name];
25 }
26}

自訂主旨

預設情況下,電子郵件的主旨就是 Notification 類別名稱的「Title Case」格式版本。所以,若 Notification 類別的名稱是 InvoicePaid,則郵件的主旨就會是 Invoide Paid。若想為訊息指定不同的主旨,可在建立郵件時呼叫 subject 方法:

1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->subject('Notification Subject')
8 ->line('...');
9}
1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->subject('Notification Subject')
8 ->line('...');
9}

自訂 Mailer

預設情況下,電子郵件通知會使用 config/mail.php 設定檔中定義的預設 Mailer。不過,也可以建立郵件時呼叫 mailer 來在執行階段使用不同的 Mailer:

1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->mailer('postmark')
8 ->line('...');
9}
1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->mailer('postmark')
8 ->line('...');
9}

自訂樣板

可以將安裝(Publish) Notification 套件的資源來修改郵件通知所使用的 HTML 樣板與純文字樣板。執行該指令後,郵件通知樣板會被放在 resources/views/vendor/notifications 目錄中:

1php artisan vendor:publish --tag=laravel-notifications
1php artisan vendor:publish --tag=laravel-notifications

附加檔案

若要將檔案附加至 E-Mail,請在建立訊息時使用 attach 方法。attach 方法接受檔案的完整路徑作為其第一個引數:

1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->greeting('Hello!')
8 ->attach('/path/to/file');
9}
1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->greeting('Hello!')
8 ->attach('/path/to/file');
9}
lightbulb

通知 Mail 訊息的 attach 方法也可傳入可附加的物件。請參考完整的可附加的物件說明文件以瞭解詳情。

將檔案附加至訊息時,也可傳入一個陣列給 attach 方法來指定要顯示的檔案名稱與 / 或 MIME 類型:

1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->greeting('Hello!')
8 ->attach('/path/to/file', [
9 'as' => 'name.pdf',
10 'mime' => 'application/pdf',
11 ]);
12}
1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->greeting('Hello!')
8 ->attach('/path/to/file', [
9 'as' => 'name.pdf',
10 'mime' => 'application/pdf',
11 ]);
12}

跟將檔案附加到 Mailable 物件不同,在 Notification 上無法使用 attachFromStorage 方法直接將存放 Disk 內的檔案附加到通知上。請使用 attach 方法,並提供該存放 Disk 中檔案的絕對路徑。或者,也可以在 toMail 方法內回傳一個 Mailable

1use App\Mail\InvoicePaid as InvoicePaidMailable;
2 
3/**
4 * Get the mail representation of the notification.
5 */
6public function toMail(object $notifiable): Mailable
7{
8 return (new InvoicePaidMailable($this->invoice))
9 ->to($notifiable->email)
10 ->attachFromStorage('/path/to/file');
11}
1use App\Mail\InvoicePaid as InvoicePaidMailable;
2 
3/**
4 * Get the mail representation of the notification.
5 */
6public function toMail(object $notifiable): Mailable
7{
8 return (new InvoicePaidMailable($this->invoice))
9 ->to($notifiable->email)
10 ->attachFromStorage('/path/to/file');
11}

若有需要,可使用 attachMany 方法來將多個檔案附加到訊息上:

1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->greeting('Hello!')
8 ->attachMany([
9 '/path/to/forge.svg',
10 '/path/to/vapor.svg' => [
11 'as' => 'Logo.svg',
12 'mime' => 'image/svg+xml',
13 ],
14 ]);
15}
1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->greeting('Hello!')
8 ->attachMany([
9 '/path/to/forge.svg',
10 '/path/to/vapor.svg' => [
11 'as' => 'Logo.svg',
12 'mime' => 'image/svg+xml',
13 ],
14 ]);
15}

原始資料附加檔案

可以使用 attachData 方法來將原始的位元組字串作為附加檔案附加到郵件上。呼叫 attachData 方法時,請提供要指派給附件的檔案名稱:

1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->greeting('Hello!')
8 ->attachData($this->pdf, 'name.pdf', [
9 'mime' => 'application/pdf',
10 ]);
11}
1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->greeting('Hello!')
8 ->attachData($this->pdf, 'name.pdf', [
9 'mime' => 'application/pdf',
10 ]);
11}

新增 Tag 與詮釋資料

有的第三方 E-Mail 提供商,如 Mailgun 或 Postmark 等,支援訊息的「Tag」與「詮釋資料」,使用 Tag 與詮釋資料,就可以對專案所送出的 E-Mail 進行分組與追蹤。可以使用 tagmetadata 屬性來為 E-Mail 訊息加上 Tag 與詮釋資料:

1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->greeting('Comment Upvoted!')
8 ->tag('upvote')
9 ->metadata('comment_id', $this->comment->id);
10}
1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->greeting('Comment Upvoted!')
8 ->tag('upvote')
9 ->metadata('comment_id', $this->comment->id);
10}

若使用 Mailgun Driver,請參考 Mailgun 說明文件中有關 Tag詮釋資料的更多資訊。同樣地,也請參考 Postmark 說明文件中有關 Tag詮釋資料的更多資料。

若使用 Amazon SES 來寄送 E-Mail,則可使用 metadata 方法來將 SES「Tag」附加到訊息上。

自訂 Symfony Message

MailMessage 類別的 withSymfonyMessage 方法可讓我們註冊一個閉包,在傳送訊息前會以 Symfony Message 實體叫用該閉包。這樣我們就有機會在郵件被送出前深度自訂該訊息:

1use Symfony\Component\Mime\Email;
2 
3/**
4 * Get the mail representation of the notification.
5 */
6public function toMail(object $notifiable): MailMessage
7{
8 return (new MailMessage)
9 ->withSymfonyMessage(function (Email $message) {
10 $message->getHeaders()->addTextHeader(
11 'Custom-Header', 'Header Value'
12 );
13 });
14}
1use Symfony\Component\Mime\Email;
2 
3/**
4 * Get the mail representation of the notification.
5 */
6public function toMail(object $notifiable): MailMessage
7{
8 return (new MailMessage)
9 ->withSymfonyMessage(function (Email $message) {
10 $message->getHeaders()->addTextHeader(
11 'Custom-Header', 'Header Value'
12 );
13 });
14}

使用 Mailable

或者,也可以在 Notification 的 toMail 方法中回傳一個完整的 Mailable 物件。若回傳的不是 MailMessage 而是 Mailable 時,就需要使用 Mailable 物件的 to 方法來指定該訊息的收件人:

1use App\Mail\InvoicePaid as InvoicePaidMailable;
2use Illuminate\Mail\Mailable;
3 
4/**
5 * Get the mail representation of the notification.
6 */
7public function toMail(object $notifiable): Mailable
8{
9 return (new InvoicePaidMailable($this->invoice))
10 ->to($notifiable->email);
11}
1use App\Mail\InvoicePaid as InvoicePaidMailable;
2use Illuminate\Mail\Mailable;
3 
4/**
5 * Get the mail representation of the notification.
6 */
7public function toMail(object $notifiable): Mailable
8{
9 return (new InvoicePaidMailable($this->invoice))
10 ->to($notifiable->email);
11}

Mailable 與隨需通知

傳送[隨需通知]時,傳給 toMail 方法的 $notifiable 實體會是 Illuminate\Notifications\AnonymousNotifiable 的實體。該實體提供了一個 routeNotificationFor 方法,可讓我們取得隨需通知要寄送的電子郵件位址:

1use App\Mail\InvoicePaid as InvoicePaidMailable;
2use Illuminate\Notifications\AnonymousNotifiable;
3use Illuminate\Mail\Mailable;
4 
5/**
6 * Get the mail representation of the notification.
7 */
8public function toMail(object $notifiable): Mailable
9{
10 $address = $notifiable instanceof AnonymousNotifiable
11 ? $notifiable->routeNotificationFor('mail')
12 : $notifiable->email;
13 
14 return (new InvoicePaidMailable($this->invoice))
15 ->to($address);
16}
1use App\Mail\InvoicePaid as InvoicePaidMailable;
2use Illuminate\Notifications\AnonymousNotifiable;
3use Illuminate\Mail\Mailable;
4 
5/**
6 * Get the mail representation of the notification.
7 */
8public function toMail(object $notifiable): Mailable
9{
10 $address = $notifiable instanceof AnonymousNotifiable
11 ? $notifiable->routeNotificationFor('mail')
12 : $notifiable->email;
13 
14 return (new InvoicePaidMailable($this->invoice))
15 ->to($address);
16}

預覽郵件通知

在設計郵件通知樣板時,若能像普通的 Blade 樣板一樣在瀏覽器中預覽轉譯後的郵件通知該有多方便。所以,在 Laravel 中,可以直接在 Route 閉包或 Controller 中回傳任何的郵件通知。若回傳郵件通知,Laravel 會轉譯該郵件通知並顯示在瀏覽器上,讓我們不需將其寄到真實的電子郵件上也能快速檢視其設計:

1use App\Models\Invoice;
2use App\Notifications\InvoicePaid;
3 
4Route::get('/notification', function () {
5 $invoice = Invoice::find(1);
6 
7 return (new InvoicePaid($invoice))
8 ->toMail($invoice->user);
9});
1use App\Models\Invoice;
2use App\Notifications\InvoicePaid;
3 
4Route::get('/notification', function () {
5 $invoice = Invoice::find(1);
6 
7 return (new InvoicePaid($invoice))
8 ->toMail($invoice->user);
9});

Markdown 的郵件通知

Markdown 的郵件通知訊息可讓我們使用郵件通知預先建立好的樣板,可讓我們自由地撰寫更長、客製化程度更高的訊息。由於使用 Markdown 來撰寫訊息,因此 Laravel 就可為這些郵件轉譯出漂亮的回應式 HTML 樣板,並自動轉譯出純文字版本的郵件。

產生訊息

若要產生有對應 Markdown 樣板的郵件通知,請使用 make:notification Artisan 指令的 --markdown 選項:

1php artisan make:notification InvoicePaid --markdown=mail.invoice.paid
1php artisan make:notification InvoicePaid --markdown=mail.invoice.paid

與其他郵件通知一樣,使用 Markdown 樣板的通知也應在 Notification 類別定義一個 toMail 方法。不過,在 Markdown 郵件通知上,我們不是使用 lineaction 方法來建立通知,而是使用 markdown 方法來指定要使用的 Markdown 樣板名稱。可傳入一組資料陣列給該方法的第二個引數來將資料提供給該樣板使用:

1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 $url = url('/invoice/'.$this->invoice->id);
7 
8 return (new MailMessage)
9 ->subject('Invoice Paid')
10 ->markdown('mail.invoice.paid', ['url' => $url]);
11}
1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 $url = url('/invoice/'.$this->invoice->id);
7 
8 return (new MailMessage)
9 ->subject('Invoice Paid')
10 ->markdown('mail.invoice.paid', ['url' => $url]);
11}

撰寫訊息

Markdown 的郵件通知混合使用了 Blade 元件與 Markdown 語法,讓我們能輕鬆地使用 Laravel 內建的通知元件來建立通知:

1<x-mail::message>
2# Invoice Paid
3 
4Your invoice has been paid!
5 
6<x-mail::button :url="$url">
7View Invoice
8</x-mail::button>
9 
10Thanks,<br>
11{{ config('app.name') }}
12</x-mail::message>
1<x-mail::message>
2# Invoice Paid
3 
4Your invoice has been paid!
5 
6<x-mail::button :url="$url">
7View Invoice
8</x-mail::button>
9 
10Thanks,<br>
11{{ config('app.name') }}
12</x-mail::message>

Button 元件

Button 元件用來轉譯一個置中的按鈕連結。這個元件接受兩個引數,一個是 url 網址,另一個則是可選的 color 顏色。支援的顏色有 primarygreenred。在通知中可以加上不限數量的 Button 元件:

1<x-mail::button :url="$url" color="green">
2View Invoice
3</x-mail::button>
1<x-mail::button :url="$url" color="green">
2View Invoice
3</x-mail::button>

Panel 元件

Panel 元件將給定的文字區塊轉譯在一個面板中,面板的底色與通知中其他部分的背景色稍有不同。我們可以使用 Panel 元件來讓給定區塊的文字較為醒目:

1<x-mail::panel>
2This is the panel content.
3</x-mail::panel>
1<x-mail::panel>
2This is the panel content.
3</x-mail::panel>

Table 元件

Table 元件可讓我們將 Markdown 表格轉為 HTML 表格。該元件接受一個 Markdown 表格作為其內容。支援使用預設的 Markdown 表格對其格式來對其表格欄位:

1<x-mail::table>
2| Laravel | Table | Example |
3| ------------- |:-------------:| --------:|
4| Col 2 is | Centered | $10 |
5| Col 3 is | Right-Aligned | $20 |
6</x-mail::table>
1<x-mail::table>
2| Laravel | Table | Example |
3| ------------- |:-------------:| --------:|
4| Col 2 is | Centered | $10 |
5| Col 3 is | Right-Aligned | $20 |
6</x-mail::table>

自訂元件

可以將所有的 Markdown 通知元件匯出到專案內來自訂這些元件。若要匯出元件,請使用 vendor:publish Artisan 指令來安裝(Publish) laravel-mail 素材標籤:

1php artisan vendor:publish --tag=laravel-mail
1php artisan vendor:publish --tag=laravel-mail

這個指令會將 Markdown 郵件元件安裝到 resources/views/vendor/mail 目錄下。mail 目錄會包含 htmltext 目錄,這些目錄中包含了所有可用元件對應的呈現方式。可以隨意自訂這些元件。

自訂 CSS

匯出元件後,resources/views/vendor/mail/html/themes 目錄下會包含一個 default.css 檔案。可以自訂這個檔案內的 CSS。這些樣式在 Markdown 通知的 HTML 呈現上會自動被轉換為內嵌的 CSS 樣式:

若想為 Laravel Markdown 元件製作一個全新的主題,可在 html/themes 目錄下放置一個 CSS 檔。命名好 CSS 檔並保存後,請修改專案 mail 設定檔中的 theme 選項為該新主題的名稱:

若要為個別通知自訂主題,可在建立通知的郵件訊息時呼叫 theme 方法。theme 方法的引數為傳送通知時要使用的主題名稱:

1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->theme('invoice')
8 ->subject('Invoice Paid')
9 ->markdown('mail.invoice.paid', ['url' => $url]);
10}
1/**
2 * Get the mail representation of the notification.
3 */
4public function toMail(object $notifiable): MailMessage
5{
6 return (new MailMessage)
7 ->theme('invoice')
8 ->subject('Invoice Paid')
9 ->markdown('mail.invoice.paid', ['url' => $url]);
10}

資料庫通知

前置要求

database 通知通道將通知資訊保存在資料庫資料表中。這個資料表會包含一些資訊,如通知類型,以及描述該通知的 JSON 資料結構。

可以查詢該資料表來將通知顯示在專案的 UI 上。不過,在這麼做之前,我們需要先建立一個用來保存通知的資料表。可以使用 notifications:table 指令來產生一個包含適當資料表結構的 Migration

1php artisan notifications:table
2 
3php artisan migrate
1php artisan notifications:table
2 
3php artisan migrate

格式化資料庫通知

若要讓某個通知支援保存在資料表中,請在該 Notification 類別上定義一個 toDatabasetoArray 方法。這個方法會收到一個 $notifiable 實體,而該方法應回傳一個純 PHP 陣列。回傳的這個陣列會被編碼為 JSON,然後保存在 notifications 資料表中的 data 欄位。來看看 toArray 方法的範例:

1/**
2 * Get the array representation of the notification.
3 *
4 * @return array<string, mixed>
5 */
6public function toArray(object $notifiable): array
7{
8 return [
9 'invoice_id' => $this->invoice->id,
10 'amount' => $this->invoice->amount,
11 ];
12}
1/**
2 * Get the array representation of the notification.
3 *
4 * @return array<string, mixed>
5 */
6public function toArray(object $notifiable): array
7{
8 return [
9 'invoice_id' => $this->invoice->id,
10 'amount' => $this->invoice->amount,
11 ];
12}

toDatabase Vs. toArray

broadcast 通道也會使用 toArray 方法來判斷要將哪些資料廣播到 JavaScript 驅動的前端上。若想讓 databasebroadcast 通道有不同的陣列呈現,請不要定義 toArray 方法,而是定義 toDatabase 方法。

存取通知

將通知保存在資料庫後,我們還需要一種方便的方法來在 Notifiable 實體上存取這些通知。Illuminate\Notifications\Notifiable Trait —— 也就是 Laravel 預設的 App\Models\User Model 中所包含的一個 Trait —— 包含了一個 notifications [Eloquent 關聯],該關聯會回傳該實體的所有通知。若要取得通知,可像其他 Eloquent 關聯一樣存取該方法。預設情況下,會使用 created_at 時戳來排序通知,最新的通知會排在 Collection 的最前面:

1$user = App\Models\User::find(1);
2 
3foreach ($user->notifications as $notification) {
4 echo $notification->type;
5}
1$user = App\Models\User::find(1);
2 
3foreach ($user->notifications as $notification) {
4 echo $notification->type;
5}

若只想取得「未讀」的通知,可使用 unreadNotifications 關聯。一樣,這些通知都會使用 created_at 來排序,最新的通知會在 Collection 的最前面:

1$user = App\Models\User::find(1);
2 
3foreach ($user->unreadNotifications as $notification) {
4 echo $notification->type;
5}
1$user = App\Models\User::find(1);
2 
3foreach ($user->unreadNotifications as $notification) {
4 echo $notification->type;
5}
lightbulb

若要在 JavaScript 用戶端中存取通知,請定義一個用來為 Notifiable 實體 (如:目前使用者) 回傳通知的 Notification Controller。接著就可以從 JavaScript 用戶端上建立一個 HTTP Request 來連線到該 Controller 的網址。

將通知標記為已讀

使用者檢視過通知後,我們通常會想將這些通知設為「已讀」。Illuminate\Notifications\Notifiable Trait 提供了一個 markAsRead 方法,該方法會更新通知資料庫記錄上的 read_at 欄位:

1$user = App\Models\User::find(1);
2 
3foreach ($user->unreadNotifications as $notification) {
4 $notification->markAsRead();
5}
1$user = App\Models\User::find(1);
2 
3foreach ($user->unreadNotifications as $notification) {
4 $notification->markAsRead();
5}

不過,我們不需要在每個通知上迴圈,可以直接在一組通知的 Collection 上使用 markAsRead 方法:

1$user->unreadNotifications->markAsRead();
1$user->unreadNotifications->markAsRead();

也可以使用批次更新(Mass-Update)查詢來將所有的通知都列為已讀,而不需要先從資料庫中取出這些通知:

1$user = App\Models\User::find(1);
2 
3$user->unreadNotifications()->update(['read_at' => now()]);
1$user = App\Models\User::find(1);
2 
3$user->unreadNotifications()->update(['read_at' => now()]);

也可以使用 delete 來從資料表中完全移除該通知:

1$user->notifications()->delete();
1$user->notifications()->delete();

廣播通知

前置要求

廣播通知前,請先設定並熟悉一下 Laravel 的事件廣播服務。使用事件廣播,就可以在 JavaScript 驅動的前端上對伺服器端 Laravel 的事件作出回應。

格式化廣播通知

broadcast 通道使用的是 Laravel 的事件廣播服務,可讓我們在 JavaScript 驅動的前端即時取得通知。若要讓通知支援廣播,請在該 Notification 類別上定義一個 toBroadcast 方法。這個方法會收到一個 $notifiable 實體,且該方法應回傳一個 BroadcastMessage 實體。若 toBroadcast 方法不存在,則會使用 toArray 方法來取得要廣播的資料。回傳的資料會被編碼為 JSON,並廣播給 JavaScript 驅動的前端。來看看一個範例的 toBroadcast 方法:

1use Illuminate\Notifications\Messages\BroadcastMessage;
2 
3/**
4 * Get the broadcastable representation of the notification.
5 */
6public function toBroadcast(object $notifiable): BroadcastMessage
7{
8 return new BroadcastMessage([
9 'invoice_id' => $this->invoice->id,
10 'amount' => $this->invoice->amount,
11 ]);
12}
1use Illuminate\Notifications\Messages\BroadcastMessage;
2 
3/**
4 * Get the broadcastable representation of the notification.
5 */
6public function toBroadcast(object $notifiable): BroadcastMessage
7{
8 return new BroadcastMessage([
9 'invoice_id' => $this->invoice->id,
10 'amount' => $this->invoice->amount,
11 ]);
12}

廣播佇列設定

所有的廣播通知都會被放入佇列以供廣播。若想為要用來廣播的佇列連線或佇列名稱,可使用 BroadcastMessageonConnection 方法與 onQueue 方法:

1return (new BroadcastMessage($data))
2 ->onConnection('sqs')
3 ->onQueue('broadcasts');
1return (new BroadcastMessage($data))
2 ->onConnection('sqs')
3 ->onQueue('broadcasts');

自訂通知類型

除了指定的資料外,所有的廣播通知也會包含一個 type 欄位,type 欄位為該通知的完整類別名稱。若想自訂通知的 type,可在該 Notification 類別上定義一個 broadcastType 方法:

1/**
2 * Get the type of the notification being broadcast.
3 */
4public function broadcastType(): string
5{
6 return 'broadcast.message';
7}
1/**
2 * Get the type of the notification being broadcast.
3 */
4public function broadcastType(): string
5{
6 return 'broadcast.message';
7}

監聽通知

通知會在使用 {notifiable}.{id} 這種命名慣例命名的私有頻道上廣播。所以,假設我們要將通知傳送給一個 ID 為 1App\Models\User 實體,則該通知會被廣播到 App.Models.User.1 私有頻道。在使用 Laravel Echo 時,只要使用 notification 方法,就可以輕鬆地在頻道上監聽通知:

1Echo.private('App.Models.User.' + userId)
2 .notification((notification) => {
3 console.log(notification.type);
4 });
1Echo.private('App.Models.User.' + userId)
2 .notification((notification) => {
3 console.log(notification.type);
4 });

自訂通知頻道

若想自訂某個 Notifiable 實體的通知要在哪個頻道上廣播,可在 Notifiable 的類別上定義一個 receivesBroadcastNotificationsOn 方法:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Broadcasting\PrivateChannel;
6use Illuminate\Foundation\Auth\User as Authenticatable;
7use Illuminate\Notifications\Notifiable;
8 
9class User extends Authenticatable
10{
11 use Notifiable;
12 
13 /**
14 * The channels the user receives notification broadcasts on.
15 */
16 public function receivesBroadcastNotificationsOn(): string
17 {
18 return 'users.'.$this->id;
19 }
20}
1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Broadcasting\PrivateChannel;
6use Illuminate\Foundation\Auth\User as Authenticatable;
7use Illuminate\Notifications\Notifiable;
8 
9class User extends Authenticatable
10{
11 use Notifiable;
12 
13 /**
14 * The channels the user receives notification broadcasts on.
15 */
16 public function receivesBroadcastNotificationsOn(): string
17 {
18 return 'users.'.$this->id;
19 }
20}

簡訊通知

前置要求

Laravel 的簡訊通知傳送功能由 Vonage 驅動。(Vonage 前身為 Nexmo)。在使用 Vonage 傳送通知前,需要先安裝 laravel/vonage-notification-channelguzzlehttp/guzzle Composer 套件::

1composer require laravel/vonage-notification-channel guzzlehttp/guzzle
1composer require laravel/vonage-notification-channel guzzlehttp/guzzle

該套件中包含了其專屬的設定檔。不過,並不需要將該設定檔安裝到專案中也可以使用該套件。只要將 VONAGE_KEYVONAGE_SECRET 環境變數設為 Vonage 的公開金鑰與私有金鑰即可。

定義好金鑰後,請將 VONAGE_SMS_FROM 設為預設要用來傳送簡訊的手機號碼。可在 Vonage 的控制面板中產生這個手機號碼:

1VONAGE_SMS_FROM=15556666666
1VONAGE_SMS_FROM=15556666666

格式化簡訊通知

若要支援以簡訊傳送通知,請在該通知類別上定義一個 toVonage 方法。這個方法會收到一個 $notifiable 實體,而回傳值應為 Illuminate\Notifications\Messages\VonageMessage 實體:

1use Illuminate\Notifications\Messages\VonageMessage;
2 
3/**
4 * Get the Vonage / SMS representation of the notification.
5 */
6public function toVonage(object $notifiable): VonageMessage
7{
8 return (new VonageMessage)
9 ->content('Your SMS message content');
10}
1use Illuminate\Notifications\Messages\VonageMessage;
2 
3/**
4 * Get the Vonage / SMS representation of the notification.
5 */
6public function toVonage(object $notifiable): VonageMessage
7{
8 return (new VonageMessage)
9 ->content('Your SMS message content');
10}

Unicode 內容

若有要傳送包含 Unicode 字元的簡訊,請在建立 VonageMessage 實體時呼叫 unicode 方法:

1use Illuminate\Notifications\Messages\VonageMessage;
2 
3/**
4 * Get the Vonage / SMS representation of the notification.
5 */
6public function toVonage(object $notifiable): VonageMessage
7{
8 return (new VonageMessage)
9 ->content('Your unicode message')
10 ->unicode();
11}
1use Illuminate\Notifications\Messages\VonageMessage;
2 
3/**
4 * Get the Vonage / SMS representation of the notification.
5 */
6public function toVonage(object $notifiable): VonageMessage
7{
8 return (new VonageMessage)
9 ->content('Your unicode message')
10 ->unicode();
11}

自訂「寄件」號碼

若想使用與 VONAGE_SMS_FROM 環境變數所設定之不同的號碼來傳送通知,可在 VonageMessage 實體上呼叫 from 方法:

1use Illuminate\Notifications\Messages\VonageMessage;
2 
3/**
4 * Get the Vonage / SMS representation of the notification.
5 */
6public function toVonage(object $notifiable): VonageMessage
7{
8 return (new VonageMessage)
9 ->content('Your SMS message content')
10 ->from('15554443333');
11}
1use Illuminate\Notifications\Messages\VonageMessage;
2 
3/**
4 * Get the Vonage / SMS representation of the notification.
5 */
6public function toVonage(object $notifiable): VonageMessage
7{
8 return (new VonageMessage)
9 ->content('Your SMS message content')
10 ->from('15554443333');
11}

新增 Client Reference

若想追蹤每位使用者產生的花費,可在通知上新增一個「Client Reference(用戶端參照)」。在 Vanage 上我們可以使用這個 Client Reference 來產生報表,以更清楚瞭解特定客戶的簡訊使用量。Client Reference 可以為最多 40 字元的任意字串:

1use Illuminate\Notifications\Messages\VonageMessage;
2 
3/**
4 * Get the Vonage / SMS representation of the notification.
5 */
6public function toVonage(object $notifiable): VonageMessage
7{
8 return (new VonageMessage)
9 ->clientReference((string) $notifiable->id)
10 ->content('Your SMS message content');
11}
1use Illuminate\Notifications\Messages\VonageMessage;
2 
3/**
4 * Get the Vonage / SMS representation of the notification.
5 */
6public function toVonage(object $notifiable): VonageMessage
7{
8 return (new VonageMessage)
9 ->clientReference((string) $notifiable->id)
10 ->content('Your SMS message content');
11}

為簡訊通知路由

若要將簡訊通知路由到正確的手機號碼,請在 Notifiable 實體上定義一個 routeNotificationForVonage 方法:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Foundation\Auth\User as Authenticatable;
6use Illuminate\Notifications\Notifiable;
7use Illuminate\Notifications\Notification;
8 
9class User extends Authenticatable
10{
11 use Notifiable;
12 
13 /**
14 * Route notifications for the Vonage channel.
15 */
16 public function routeNotificationForVonage(Notification $notification): string
17 {
18 return $this->phone_number;
19 }
20}
1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Foundation\Auth\User as Authenticatable;
6use Illuminate\Notifications\Notifiable;
7use Illuminate\Notifications\Notification;
8 
9class User extends Authenticatable
10{
11 use Notifiable;
12 
13 /**
14 * Route notifications for the Vonage channel.
15 */
16 public function routeNotificationForVonage(Notification $notification): string
17 {
18 return $this->phone_number;
19 }
20}

Slack 通知

前置要求

在開始使用 Slack 傳送通知前,請先使用 Composer 安裝 Slack 通知通道:

1composer require laravel/slack-notification-channel
1composer require laravel/slack-notification-channel

此外,也許為 Slack 團隊建立一個 Slack App。建立好 App 後,請為該工作空間建立一個「傳入的 WebHook」。建立之後,Slack 會提供一個 WebHook URL,在為 Slack 通知路由時會使用到該 URL。

格式化 Slack 通知

若要讓通知支援以 Slack 訊息傳送,請在該 Notification 類別上定義一個 toSlack 方法。這個方法會收到一個 $notifiable 實體,而該方法應回傳 Illuminate\Notifications\Messages\SlackMessage 實體。Slack 訊息可以包含文字內容,也可以包含一個「Attachment(附件)」。Attachment 就是格式化過的額外文字,或是一組欄位的陣列。讓我們來看看一個基礎的 toSlack 範例:

1use Illuminate\Notifications\Messages\SlackMessage;
2 
3/**
4 * Get the Slack representation of the notification.
5 */
6public function toSlack(object $notifiable): SlackMessage
7{
8 return (new SlackMessage)
9 ->content('One of your invoices has been paid!');
10}
1use Illuminate\Notifications\Messages\SlackMessage;
2 
3/**
4 * Get the Slack representation of the notification.
5 */
6public function toSlack(object $notifiable): SlackMessage
7{
8 return (new SlackMessage)
9 ->content('One of your invoices has been paid!');
10}

Slack Attachment

也可以在 Slack 訊息上加入「Attachment」。Attachment 比起簡單的文字訊息,有更豐富的格式可使用。在這個範例中,我們會傳送一個有關程式內發生 Exception 的錯誤訊息,其中包含了一個可用來檢視該 Exception 詳情的連結:

1use Illuminate\Notifications\Messages\SlackAttachment;
2use Illuminate\Notifications\Messages\SlackMessage;
3 
4/**
5 * Get the Slack representation of the notification.
6 */
7public function toSlack(object $notifiable): SlackMessage
8{
9 $url = url('/exceptions/'.$this->exception->id);
10 
11 return (new SlackMessage)
12 ->error()
13 ->content('Whoops! Something went wrong.')
14 ->attachment(function (SlackAttachment $attachment) use ($url) {
15 $attachment->title('Exception: File Not Found', $url)
16 ->content('File [background.jpg] was not found.');
17 });
18}
1use Illuminate\Notifications\Messages\SlackAttachment;
2use Illuminate\Notifications\Messages\SlackMessage;
3 
4/**
5 * Get the Slack representation of the notification.
6 */
7public function toSlack(object $notifiable): SlackMessage
8{
9 $url = url('/exceptions/'.$this->exception->id);
10 
11 return (new SlackMessage)
12 ->error()
13 ->content('Whoops! Something went wrong.')
14 ->attachment(function (SlackAttachment $attachment) use ($url) {
15 $attachment->title('Exception: File Not Found', $url)
16 ->content('File [background.jpg] was not found.');
17 });
18}

使用 Attachment 時也可以指定一組用來顯示給使用者的資料陣列。給定的資料會以表格形式呈現以讓使用者輕鬆閱讀:

1use Illuminate\Notifications\Messages\SlackAttachment;
2use Illuminate\Notifications\Messages\SlackMessage;
3 
4/**
5 * Get the Slack representation of the notification.
6 */
7public function toSlack(object $notifiable): SlackMessage
8{
9 $url = url('/invoices/'.$this->invoice->id);
10 
11 return (new SlackMessage)
12 ->success()
13 ->content('One of your invoices has been paid!')
14 ->attachment(function (SlackAttachment $attachment) use ($url) {
15 $attachment->title('Invoice 1322', $url)
16 ->fields([
17 'Title' => 'Server Expenses',
18 'Amount' => '$1,234',
19 'Via' => 'American Express',
20 'Was Overdue' => ':-1:',
21 ]);
22 });
23}
1use Illuminate\Notifications\Messages\SlackAttachment;
2use Illuminate\Notifications\Messages\SlackMessage;
3 
4/**
5 * Get the Slack representation of the notification.
6 */
7public function toSlack(object $notifiable): SlackMessage
8{
9 $url = url('/invoices/'.$this->invoice->id);
10 
11 return (new SlackMessage)
12 ->success()
13 ->content('One of your invoices has been paid!')
14 ->attachment(function (SlackAttachment $attachment) use ($url) {
15 $attachment->title('Invoice 1322', $url)
16 ->fields([
17 'Title' => 'Server Expenses',
18 'Amount' => '$1,234',
19 'Via' => 'American Express',
20 'Was Overdue' => ':-1:',
21 ]);
22 });
23}

Markdown 的 Attachment 內容

若有 Attachment 欄位包含 Markdown,可使用 markdown 方法來讓 Slack 以 Markdown 格式解析並顯示給定的欄位。該方法接受的值有:pretexttextfields。有關 Slack Attachment 格式的更多資訊,請參考 Slack API 說明文件

1use Illuminate\Notifications\Messages\SlackAttachment;
2use Illuminate\Notifications\Messages\SlackMessage;
3 
4/**
5 * Get the Slack representation of the notification.
6 */
7public function toSlack(object $notifiable): SlackMessage
8{
9 $url = url('/exceptions/'.$this->exception->id);
10 
11 return (new SlackMessage)
12 ->error()
13 ->content('Whoops! Something went wrong.')
14 ->attachment(function (SlackAttachment $attachment) use ($url) {
15 $attachment->title('Exception: File Not Found', $url)
16 ->content('File [background.jpg] was *not found*.')
17 ->markdown(['text']);
18 });
19}
1use Illuminate\Notifications\Messages\SlackAttachment;
2use Illuminate\Notifications\Messages\SlackMessage;
3 
4/**
5 * Get the Slack representation of the notification.
6 */
7public function toSlack(object $notifiable): SlackMessage
8{
9 $url = url('/exceptions/'.$this->exception->id);
10 
11 return (new SlackMessage)
12 ->error()
13 ->content('Whoops! Something went wrong.')
14 ->attachment(function (SlackAttachment $attachment) use ($url) {
15 $attachment->title('Exception: File Not Found', $url)
16 ->content('File [background.jpg] was *not found*.')
17 ->markdown(['text']);
18 });
19}

為 Slack 通知路由

若要將 Slack 通知路由到正確的 Slack 團隊與頻道,請在 Notifiable 實體上定義一個 routeNotificationForSlack 方法。該方法應回傳該通知要傳送的 WebHook URL。若要產生 WebHook URL,可在 Slack 團隊中新增一個「傳入的 WebHook」:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Foundation\Auth\User as Authenticatable;
6use Illuminate\Notifications\Notifiable;
7use Illuminate\Notifications\Notification;
8 
9class User extends Authenticatable
10{
11 use Notifiable;
12 
13 /**
14 * Route notifications for the Slack channel.
15 */
16 public function routeNotificationForSlack(Notification $notification): string
17 {
18 return 'https://hooks.slack.com/services/...';
19 }
20}
1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Foundation\Auth\User as Authenticatable;
6use Illuminate\Notifications\Notifiable;
7use Illuminate\Notifications\Notification;
8 
9class User extends Authenticatable
10{
11 use Notifiable;
12 
13 /**
14 * Route notifications for the Slack channel.
15 */
16 public function routeNotificationForSlack(Notification $notification): string
17 {
18 return 'https://hooks.slack.com/services/...';
19 }
20}

本土化通知

在 Laravel 中,可以使用與 Request 中不同的語系設定來傳送通知。通知被放入佇列後依然會使用所設定的語系。

若要設定語系,請使用 Illuminate\Notifications\Notification 類別提供的 locale 方法來設定要使用的語言。在取得通知內容時,程式會先進入這個語系中,取完內容後再回到之前的語系:

1$user->notify((new InvoicePaid($invoice))->locale('es'));
1$user->notify((new InvoicePaid($invoice))->locale('es'));

可以使用 Notification Facade 來本地化多個 Notifiable 實體:

1Notification::locale('es')->send(
2 $users, new InvoicePaid($invoice)
3);
1Notification::locale('es')->send(
2 $users, new InvoicePaid($invoice)
3);

使用者偏好的語系

有時候,我們的程式會儲存每個使用者偏好的語言。只要在一個或多個 Notifiable Model 上實作 HasLocalePreference Contract,就可以讓 Laravel 在傳送通知時使用這些儲存的語系:

1use Illuminate\Contracts\Translation\HasLocalePreference;
2 
3class User extends Model implements HasLocalePreference
4{
5 /**
6 * Get the user's preferred locale.
7 */
8 public function preferredLocale(): string
9 {
10 return $this->locale;
11 }
12}
1use Illuminate\Contracts\Translation\HasLocalePreference;
2 
3class User extends Model implements HasLocalePreference
4{
5 /**
6 * Get the user's preferred locale.
7 */
8 public function preferredLocale(): string
9 {
10 return $this->locale;
11 }
12}

實作好該介面後,向該 Model 傳送通知或 Mailable 時,Laravel 就會自動使用偏好的語系。因此,使用該介面時不需呼叫 locale 方法:

1$user->notify(new InvoicePaid($invoice));
1$user->notify(new InvoicePaid($invoice));

測試

可以使用 Notification Facade 的 fake 方法來避免送出 Notification。一般來說,送出 Notification 與實際要測試的程式碼是不相關的。通常,只要判斷 Laravel 是否有接到指示要送出給定的 Notification 就夠了。

呼叫 Notification Facade 的 fake 方法後,就可以判斷是否有被要求要將該 Notification 送出給使用者,甚至還能判斷 Notification 收到的資料:

1<?php
2 
3namespace Tests\Feature;
4 
5use App\Notifications\OrderShipped;
6use Illuminate\Support\Facades\Notification;
7use Tests\TestCase;
8 
9class ExampleTest extends TestCase
10{
11 public function test_orders_can_be_shipped(): void
12 {
13 Notification::fake();
14 
15 // 處理訂單出貨...
16 
17 // 判斷未有 Notification 被送出...
18 Notification::assertNothingSent();
19 
20 // 判斷某個 Notification 是否被傳送至給定的使用者...
21 Notification::assertSentTo(
22 [$user], OrderShipped::class
23 );
24 
25 // 判斷某個 Notification 是否未被送出...
26 Notification::assertNotSentTo(
27 [$user], AnotherNotification::class
28 );
29 
30 // 測試送出了給定數量的 Notification...
31 Notification::assertCount(3);
32 }
33}
1<?php
2 
3namespace Tests\Feature;
4 
5use App\Notifications\OrderShipped;
6use Illuminate\Support\Facades\Notification;
7use Tests\TestCase;
8 
9class ExampleTest extends TestCase
10{
11 public function test_orders_can_be_shipped(): void
12 {
13 Notification::fake();
14 
15 // 處理訂單出貨...
16 
17 // 判斷未有 Notification 被送出...
18 Notification::assertNothingSent();
19 
20 // 判斷某個 Notification 是否被傳送至給定的使用者...
21 Notification::assertSentTo(
22 [$user], OrderShipped::class
23 );
24 
25 // 判斷某個 Notification 是否未被送出...
26 Notification::assertNotSentTo(
27 [$user], AnotherNotification::class
28 );
29 
30 // 測試送出了給定數量的 Notification...
31 Notification::assertCount(3);
32 }
33}

可以傳入一個閉包給 assertSentToassertNotSentTo 方法,來判斷某個 Notification 是否通過給定的「真值測試 (Truth Test)」。若送出的 Notification 中至少有一個 Notification 通過給定的真值測試,則該 Assertion 會被視為成功:

1Notification::assertSentTo(
2 $user,
3 function (OrderShipped $notification, array $channels) use ($order) {
4 return $notification->order->id === $order->id;
5 }
6);
1Notification::assertSentTo(
2 $user,
3 function (OrderShipped $notification, array $channels) use ($order) {
4 return $notification->order->id === $order->id;
5 }
6);

隨需通知

若要測試的程式中有傳送隨需通知,則可使用 assertSentOnDemand 方法來測試是否有送出隨需通知:

1Notification::assertSentOnDemand(OrderShipped::class);
1Notification::assertSentOnDemand(OrderShipped::class);

若在 assertSentOnDemand 方法的第二個引數上傳入閉包,就能判斷隨需通知是否被送給正確的「Route(路由)」位址:

1Notification::assertSentOnDemand(
2 OrderShipped::class,
3 function (OrderShipped $notification, array $channels, object $notifiable) use ($user) {
4 return $notifiable->routes['mail'] === $user->email;
5 }
6);
1Notification::assertSentOnDemand(
2 OrderShipped::class,
3 function (OrderShipped $notification, array $channels, object $notifiable) use ($user) {
4 return $notifiable->routes['mail'] === $user->email;
5 }
6);

通知事件

傳送中事件 - NotificationSending

在傳送通知時,通知系統會分派一個 Illuminate\Notifications\Events\NotificationSending 事件。該 Event 中包含了一個「Notifiable」實體,以及通知實體本身。可以在專案的 EventServiceProvider 中為該 Event 註冊 Listener:

1use App\Listeners\CheckNotificationStatus;
2use Illuminate\Notifications\Events\NotificationSending;
3 
4/**
5 * The event listener mappings for the application.
6 *
7 * @var array
8 */
9protected $listen = [
10 NotificationSending::class => [
11 CheckNotificationStatus::class,
12 ],
13];
1use App\Listeners\CheckNotificationStatus;
2use Illuminate\Notifications\Events\NotificationSending;
3 
4/**
5 * The event listener mappings for the application.
6 *
7 * @var array
8 */
9protected $listen = [
10 NotificationSending::class => [
11 CheckNotificationStatus::class,
12 ],
13];

NotificationSending 事件的任一監聽程式中 handle 方法回傳 false,就不會傳送該通知:

1use Illuminate\Notifications\Events\NotificationSending;
2 
3/**
4 * Handle the event.
5 */
6public function handle(NotificationSending $event): void
7{
8 return false;
9}
1use Illuminate\Notifications\Events\NotificationSending;
2 
3/**
4 * Handle the event.
5 */
6public function handle(NotificationSending $event): void
7{
8 return false;
9}

在 Event Listener 中,可以在該 Event 上存取 notifiablenotificationchannel 等屬性,以取得更多有關通知收件人或通知本身的資訊:

1/**
2 * Handle the event.
3 */
4public function handle(NotificationSending $event): void
5{
6 // $event->channel
7 // $event->notifiable
8 // $event->notification
9}
1/**
2 * Handle the event.
3 */
4public function handle(NotificationSending $event): void
5{
6 // $event->channel
7 // $event->notifiable
8 // $event->notification
9}

已傳送事件 - NotificationSent

在傳送通知時,通知系統會分派一個 Illuminate\Notifications\Events\NotificationSent 事件。該 Event 中包含了一個「Notifiable」實體,以及通知實體本身。可以在專案的 EventServiceProvider 中為該 Event 註冊 Listener:

1use App\Listeners\LogNotification;
2use Illuminate\Notifications\Events\NotificationSent;
3 
4/**
5 * The event listener mappings for the application.
6 *
7 * @var array
8 */
9protected $listen = [
10 NotificationSent::class => [
11 LogNotification::class,
12 ],
13];
1use App\Listeners\LogNotification;
2use Illuminate\Notifications\Events\NotificationSent;
3 
4/**
5 * The event listener mappings for the application.
6 *
7 * @var array
8 */
9protected $listen = [
10 NotificationSent::class => [
11 LogNotification::class,
12 ],
13];
lightbulb

EventServiceProvider 中註冊好 Listener 後,可使用 event:generate Artisan 指令來快速產生 Listener 類別。

在 Event Listener 中,可以在該 Event 上存取 notifiablenotificationchannelresponse 等屬性,以取得更多有關通知收件人或通知本身的資訊:

1/**
2 * Handle the event.
3 */
4public function handle(NotificationSent $event): void
5{
6 // $event->channel
7 // $event->notifiable
8 // $event->notification
9 // $event->response
10}
1/**
2 * Handle the event.
3 */
4public function handle(NotificationSent $event): void
5{
6 // $event->channel
7 // $event->notifiable
8 // $event->notification
9 // $event->response
10}

自訂通道

Laravel 中隨附了許多通知通道,不過,我們也可以自行撰寫自訂的 Driver 來使用其他通道傳送通知。在 Laravel 中,要製作自訂 Driver 非常簡單。要開始製作自訂 Driver,請先定義一個包含 send 方法的類別。該方法應接收兩個引數:$notifiable$notification

send 方法中,我們可以呼叫 Notification 上的方法,以取得這個頻道能理解的訊息物件,然後再將通知傳送給 $notifiable 實體:

1<?php
2 
3namespace App\Notifications;
4 
5use Illuminate\Notifications\Notification;
6 
7class VoiceChannel
8{
9 /**
10 * Send the given notification.
11 */
12 public function send(object $notifiable, Notification $notification): void
13 {
14 $message = $notification->toVoice($notifiable);
15 
16 // 傳送通知給 $notifiable 實體...
17 }
18}
1<?php
2 
3namespace App\Notifications;
4 
5use Illuminate\Notifications\Notification;
6 
7class VoiceChannel
8{
9 /**
10 * Send the given notification.
11 */
12 public function send(object $notifiable, Notification $notification): void
13 {
14 $message = $notification->toVoice($notifiable);
15 
16 // 傳送通知給 $notifiable 實體...
17 }
18}

定義好通知通道後,接著就可以在任何 Notification 類別內的 via 方法中回傳我們自訂 Driver 的類別名稱。在這個範例中,Notification 的 toVoice 方法可以回傳任何要用來代表語音(Voice)訊息的物件。舉例來說,我們可以定義一個自訂的 VoiceMessage 類別來代表這些訊息:

1<?php
2 
3namespace App\Notifications;
4 
5use App\Notifications\Messages\VoiceMessage;
6use App\Notifications\VoiceChannel;
7use Illuminate\Bus\Queueable;
8use Illuminate\Contracts\Queue\ShouldQueue;
9use Illuminate\Notifications\Notification;
10 
11class InvoicePaid extends Notification
12{
13 use Queueable;
14 
15 /**
16 * Get the notification channels.
17 */
18 public function via(object $notifiable): string
19 {
20 return VoiceChannel::class;
21 }
22 
23 /**
24 * Get the voice representation of the notification.
25 */
26 public function toVoice(object $notifiable): VoiceMessage
27 {
28 // ...
29 }
30}
1<?php
2 
3namespace App\Notifications;
4 
5use App\Notifications\Messages\VoiceMessage;
6use App\Notifications\VoiceChannel;
7use Illuminate\Bus\Queueable;
8use Illuminate\Contracts\Queue\ShouldQueue;
9use Illuminate\Notifications\Notification;
10 
11class InvoicePaid extends Notification
12{
13 use Queueable;
14 
15 /**
16 * Get the notification channels.
17 */
18 public function via(object $notifiable): string
19 {
20 return VoiceChannel::class;
21 }
22 
23 /**
24 * Get the voice representation of the notification.
25 */
26 public function toVoice(object $notifiable): VoiceMessage
27 {
28 // ...
29 }
30}
翻譯進度
100% 已翻譯
更新時間:
2024年6月30日 清晨7:45:00 [世界標準時間]
翻譯人員:
  • cornch
幫我們翻譯此頁

留言

尚無留言

“Laravel” is a Trademark of Taylor Otwell.
The source documentation is released under MIT license. See laravel/docs on GitHub for details.
The translated documentations are released under MIT license. See cornch/laravel-docs-l10n on GitHub for details.