通知 - 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
方法與不定數量的訊息建立方法,如 toMail
或 toDatabase
,這些訊息建立方法將通知轉換為特定頻道格式的訊息。
傳送通知
使用 Notifiable Trait
有兩種方法可以傳送通知:使用 Notifiable
Trait 的 notify
方法,或是使用 Notification
Facade。Notifiable
Trait 已預設包含在專案的 App\Models\User
中:
1<?php23namespace App\Models;45use Illuminate\Foundation\Auth\User as Authenticatable;6use Illuminate\Notifications\Notifiable;78class User extends Authenticatable9{10 use Notifiable;11}
1<?php23namespace App\Models;45use Illuminate\Foundation\Auth\User as Authenticatable;6use Illuminate\Notifications\Notifiable;78class User extends Authenticatable9{10 use Notifiable;11}
該 Trait 提供的 notify
方法預期接收一個通知實體:
1use App\Notifications\InvoicePaid;23$user->notify(new InvoicePaid($invoice));
1use App\Notifications\InvoicePaid;23$user->notify(new InvoicePaid($invoice));
請記得,任何的 Model 都可以使用 Notifiable
Trait。不是只有 User
Model 上才能用。
使用 Notification Facade
或者,也可以使用 Nofication
[Facade] 來傳送通知。若要傳送通知給多個 Notifiable 實體 (如一組 User Collection),就很適合這種方法。若要使用該 Facade 傳送通知,請將所有 Notifiable 實體與 Notification 實體傳給 send
方法:
1use Illuminate\Support\Facades\Notification;23Notification::send($users, new InvoicePaid($invoice));
1use Illuminate\Support\Facades\Notification;23Notification::send($users, new InvoicePaid($invoice));
也可以使用 sendNow
方法來馬上傳送通知。即使通知有實作 ShouldQueue
介面,該方法也會立即傳送通知:
1Notification::sendNow($developers, new DeploymentCompleted($deployment));
1Notification::sendNow($developers, new DeploymentCompleted($deployment));
指定傳送通道
每個 Notification 類別都有一個 via
方法,用來判斷該通知要在哪些通道上傳送。通知在 mail
、database
、broadcast
、vonage
、slack
等通道上傳送:
若想使用其他通道傳送,如 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): array7{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): array7{8 return $notifiable->prefers_sms ? ['vonage'] : ['mail', 'database'];9}
將通知放入佇列
在將通知放入佇列前,請先設定好佇列並執行一個 Worker。
傳送通知可能會需要花費時間,特別是需要使用外部 API 呼叫來傳送通知的頻道。若要加速程式的回應時間,可在通知類別上加入 ShouldQueue
介面與 Queueable
Trait 來讓通知使用佇列。使用 make:notification
指令產生的通知中,預設已有匯入該介面與 Trait,因此我們可以直接將其加入通知類別:
1<?php23namespace App\Notifications;45use Illuminate\Bus\Queueable;6use Illuminate\Contracts\Queue\ShouldQueue;7use Illuminate\Notifications\Notification;89class InvoicePaid extends Notification implements ShouldQueue10{11 use Queueable;1213 // ...14}
1<?php23namespace App\Notifications;45use Illuminate\Bus\Queueable;6use Illuminate\Contracts\Queue\ShouldQueue;7use Illuminate\Notifications\Notification;89class InvoicePaid extends Notification implements ShouldQueue10{11 use Queueable;1213 // ...14}
將 ShouldQueue
介面加入通知後,可像平常一樣傳送通知。Laravel 會在偵測到該類別有 ShouldQueue
介面後自動以佇列寄送通知:
1$user->notify(new InvoicePaid($invoice));
1$user->notify(new InvoicePaid($invoice));
在將通知放入佇列時,Laravel 會為每個收件人與每個通道的組合建立佇列任務。舉例來說,若通知有三個收件人與兩個通道,則會派發六個任務。
延遲通知
若想延遲傳送通知,可在通知初始化之後串聯呼叫 delay
方法:
1$delay = now()->addMinutes(10);23$user->notify((new InvoicePaid($invoice))->delay($delay));
1$delay = now()->addMinutes(10);23$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): array7{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): array7{8 return [9 'mail' => now()->addMinutes(5),10 'sms' => now()->addMinutes(10),11 ];12}
自訂通知的佇列連線
預設情況下,放入佇列的通知會被放進專案的預設佇列連線中。若想為特定的通知指定不同的連線,可在 Notification 的 建構 Constructor 上呼叫 onConnection
方法:
1<?php23namespace App\Notifications;45use Illuminate\Bus\Queueable;6use Illuminate\Contracts\Queue\ShouldQueue;7use Illuminate\Notifications\Notification;89class InvoicePaid extends Notification implements ShouldQueue10{11 use Queueable;1213 /**14 * Create a new notification instance.15 */16 public function __construct()17 {18 $this->onConnection('redis');19 }20}
1<?php23namespace App\Notifications;45use Illuminate\Bus\Queueable;6use Illuminate\Contracts\Queue\ShouldQueue;7use Illuminate\Notifications\Notification;89class InvoicePaid extends Notification implements ShouldQueue10{11 use Queueable;1213 /**14 * Create a new notification instance.15 */16 public function __construct()17 {18 $this->onConnection('redis');19 }20}
或者,若想為通知所支援的各個通知通道個別指定佇列連線,可在通知上定義一個 viaConnections
方法。這個方法應回傳一組通道名稱 / 佇列名稱配對的陣列:
1/**2 * Determine which connections should be used for each notification channel.3 *4 * @return array<string, string>5 */6public function viaConnections(): array7{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(): array7{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(): array7{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(): array7{8 return [9 'mail' => 'mail-queue',10 'slack' => 'slack-queue',11 ];12}
佇列的通知與資料庫 Transaction
當佇列通知是在資料庫 Transaction 內分派的時候,這個通知可能會在資料庫 Transaction 被 Commit 前就被佇列進行處理了。發生這種情況時,在資料庫 Transaction 期間對 Model 或資料庫記錄所做出的更新可能都還未反應到資料庫內。另外,所有在 Transaction 期間新增的 Model 或資料庫記錄也可能還未出現在資料庫內。若該通知有使用這些 Model 的話,處理該通知的佇列任務時可能會出現未預期的錯誤。
若佇列的 after_commit
選項設為 false
,則我們還是可以通過在傳送通知前呼叫 afterCommit
方法來標示出該 Mailable 應在所有資料庫 Transaction 都被 Commit 後才分派:
1use App\Notifications\InvoicePaid;23$user->notify((new InvoicePaid($invoice))->afterCommit());
1use App\Notifications\InvoicePaid;23$user->notify((new InvoicePaid($invoice))->afterCommit());
或者,也可以在 Notification 的 Constructor 上呼叫 afterCommit
方法:
1<?php23namespace App\Notifications;45use Illuminate\Bus\Queueable;6use Illuminate\Contracts\Queue\ShouldQueue;7use Illuminate\Notifications\Notification;89class InvoicePaid extends Notification implements ShouldQueue10{11 use Queueable;1213 /**14 * Create a new notification instance.15 */16 public function __construct()17 {18 $this->afterCommit();19 }20}
1<?php23namespace App\Notifications;45use Illuminate\Bus\Queueable;6use Illuminate\Contracts\Queue\ShouldQueue;7use Illuminate\Notifications\Notification;89class InvoicePaid extends Notification implements ShouldQueue10{11 use Queueable;1213 /**14 * Create a new notification instance.15 */16 public function __construct()17 {18 $this->afterCommit();19 }20}
要瞭解更多有關這類問題的解決方法,請參考有關佇列任務與資料庫 Transaction 有關的說明文件。
判斷是否應送出某個佇列的通知
當佇列通知被派發出去給背景執行後,通常會被傳給佇列 Worker 並進一步傳送給指定的收件人。
不過,在通知要被佇列 Worker 處理時,若我們還想就是否應傳送該佇列通知作最後的決定,可在該 Notification 類別上定義一個 shouldSend
方法。若該方法回傳 false
,就不會傳送這個通知:
1/**2 * Determine if the notification should be sent.3 */4public function shouldSend(object $notifiable, string $channel): bool5{6 return $this->invoice->isPaid();7}
1/**2 * Determine if the notification should be sent.3 */4public function shouldSend(object $notifiable, string $channel): bool5{6 return $this->invoice->isPaid();7}
隨需通知
有時候,我們會需要將通知傳給不是我們網站使用者的人。只要使用 Notification
Facade 上的 route
方法,就可以在送出通知前指定特別的通知 Route 資訊:
1use Illuminate\Broadcasting\Channel;2use Illuminate\Support\Facades\Notification;35 ->route('vonage', '5555555555')6 ->route('slack', '#slack-channel')7 ->route('broadcast', [new Channel('channel-name')])8 ->notify(new InvoicePaid($invoice));
1use Illuminate\Broadcasting\Channel;2use Illuminate\Support\Facades\Notification;35 ->route('vonage', '5555555555')6 ->route('slack', '#slack-channel')7 ->route('broadcast', [new Channel('channel-name')])8 ->notify(new InvoicePaid($invoice));
若想在傳送隨需通知時為 mail
Route 提供收件人名稱,可提供一個索引鍵為郵件位址而值為姓名的陣列:
1Notification::route('mail', [3])->notify(new InvoicePaid($invoice));
1Notification::route('mail', [3])->notify(new InvoicePaid($invoice));
郵件通知
格式化郵件通知
若要支援以電子郵件傳送通知,請在該通知類別上定義一個 toMail
方法。這個方法會收到一個 $notifiable
實體,而回傳值應為 Illuminate\Notifications\Messages\MailMessage
實體。
MailMessage
類別包含一些簡單的方法,可以讓我們快速建立交易電子郵件訊息。郵件訊息可包含數行的文字,以及「執行動作」。來看看一個範例的 toMail
方法:
1/**2 * Get the mail representation of the notification.3 */4public function toMail(object $notifiable): MailMessage5{6 $url = url('/invoice/'.$this->invoice->id);78 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): MailMessage5{6 $url = url('/invoice/'.$this->invoice->id);78 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}
請注意,在 toMail
中,我們使用了 $this->invoice->id
。我們可以將通知訊息所需要的任何資料傳入該通知的 Constructor 中。
在這個範例中,我們註冊了一個招呼語,一行文字,一個動作,然後是又一行的文字。MailMessage
物件提供的這些方法讓我們可以簡單快速地格式化簡短的交易電子郵件。Mail 通道會將該這些訊息元件翻譯為漂亮的回應式 HTML 電子郵件樣板與一個回應的純文字版本。下列是 mail
通道產生的電子郵件範例:
在傳送郵件通知時,請確保有在 config/app.php
設定檔中設定 name
設定選項。在郵件通知訊息的頁頭與頁尾中會使用到這個值。
錯誤訊息
有些通知是用來通知使用者錯誤的,如付款失敗等。可在建立訊息時呼叫 error
方法來標示該郵件訊息是錯誤通知。在郵件訊息上使用 error
方法時,動作按鈕會從黑色變成紅色的:
1/**2 * Get the mail representation of the notification.3 */4public function toMail(object $notifiable): MailMessage5{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): MailMessage5{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): MailMessage5{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): MailMessage5{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): MailMessage5{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): MailMessage5{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): MailMessage5{6 return (new MailMessage)8 ->line('...');9}
1/**2 * Get the mail representation of the notification.3 */4public function toMail(object $notifiable): MailMessage5{6 return (new MailMessage)8 ->line('...');9}
自訂收件人
使用 mail
通道傳送通知時,通知系統會自動在 Notifiable 實體上尋找 email
屬性。可在該 Notifiable 實體上定義一個 routeNotificationForMail
方法來自訂通知要傳送給哪個電子郵件位址:
1<?php23namespace App\Models;45use Illuminate\Foundation\Auth\User as Authenticatable;6use Illuminate\Notifications\Notifiable;7use Illuminate\Notifications\Notification;89class User extends Authenticatable10{11 use Notifiable;1213 /**14 * Route notifications for the mail channel.15 *16 * @return array<string, string>|string17 */18 public function routeNotificationForMail(Notification $notification): array|string19 {20 // 只回傳 E-Mail 位址...21 return $this->email_address;2223 // 回傳 E-Mail 位址與名稱...24 return [$this->email_address => $this->name];25 }26}
1<?php23namespace App\Models;45use Illuminate\Foundation\Auth\User as Authenticatable;6use Illuminate\Notifications\Notifiable;7use Illuminate\Notifications\Notification;89class User extends Authenticatable10{11 use Notifiable;1213 /**14 * Route notifications for the mail channel.15 *16 * @return array<string, string>|string17 */18 public function routeNotificationForMail(Notification $notification): array|string19 {20 // 只回傳 E-Mail 位址...21 return $this->email_address;2223 // 回傳 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): MailMessage5{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): MailMessage5{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): MailMessage5{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): MailMessage5{6 return (new MailMessage)7 ->mailer('postmark')8 ->line('...');9}
自訂樣板
可以將安裝 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): MailMessage5{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): MailMessage5{6 return (new MailMessage)7 ->greeting('Hello!')8 ->attach('/path/to/file');9}
通知 Mail 訊息的 attach
方法也可傳入可附加的物件。請參考完整的可附加的物件說明文件以瞭解詳情。
將檔案附加至訊息時,也可傳入一個陣列給 attach
方法來指定要顯示的檔案名稱與 / 或 MIME 類型:
1/**2 * Get the mail representation of the notification.3 */4public function toMail(object $notifiable): MailMessage5{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): MailMessage5{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;23/**4 * Get the mail representation of the notification.5 */6public function toMail(object $notifiable): Mailable7{8 return (new InvoicePaidMailable($this->invoice))9 ->to($notifiable->email)10 ->attachFromStorage('/path/to/file');11}
1use App\Mail\InvoicePaid as InvoicePaidMailable;23/**4 * Get the mail representation of the notification.5 */6public function toMail(object $notifiable): Mailable7{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): MailMessage5{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): MailMessage5{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): MailMessage5{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): MailMessage5{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 進行分組與追蹤。可以使用 tag
與 metadata
屬性來為 E-Mail 訊息加上 Tag 與詮釋資料:
1/**2 * Get the mail representation of the notification.3 */4public function toMail(object $notifiable): MailMessage5{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): MailMessage5{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;23/**4 * Get the mail representation of the notification.5 */6public function toMail(object $notifiable): MailMessage7{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;23/**4 * Get the mail representation of the notification.5 */6public function toMail(object $notifiable): MailMessage7{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;34/**5 * Get the mail representation of the notification.6 */7public function toMail(object $notifiable): Mailable8{9 return (new InvoicePaidMailable($this->invoice))10 ->to($notifiable->email);11}
1use App\Mail\InvoicePaid as InvoicePaidMailable;2use Illuminate\Mail\Mailable;34/**5 * Get the mail representation of the notification.6 */7public function toMail(object $notifiable): Mailable8{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;45/**6 * Get the mail representation of the notification.7 */8public function toMail(object $notifiable): Mailable9{10 $address = $notifiable instanceof AnonymousNotifiable11 ? $notifiable->routeNotificationFor('mail')12 : $notifiable->email;1314 return (new InvoicePaidMailable($this->invoice))15 ->to($address);16}
1use App\Mail\InvoicePaid as InvoicePaidMailable;2use Illuminate\Notifications\AnonymousNotifiable;3use Illuminate\Mail\Mailable;45/**6 * Get the mail representation of the notification.7 */8public function toMail(object $notifiable): Mailable9{10 $address = $notifiable instanceof AnonymousNotifiable11 ? $notifiable->routeNotificationFor('mail')12 : $notifiable->email;1314 return (new InvoicePaidMailable($this->invoice))15 ->to($address);16}
預覽郵件通知
在設計郵件通知樣板時,若能像普通的 Blade 樣板一樣在瀏覽器中預覽轉譯後的郵件通知該有多方便。所以,在 Laravel 中,可以直接在 Route 閉包或 Controller 中回傳任何的郵件通知。若回傳郵件通知,Laravel 會轉譯該郵件通知並顯示在瀏覽器上,讓我們不需將其寄到真實的電子郵件上也能快速檢視其設計:
1use App\Models\Invoice;2use App\Notifications\InvoicePaid;34Route::get('/notification', function () {5 $invoice = Invoice::find(1);67 return (new InvoicePaid($invoice))8 ->toMail($invoice->user);9});
1use App\Models\Invoice;2use App\Notifications\InvoicePaid;34Route::get('/notification', function () {5 $invoice = Invoice::find(1);67 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 郵件通知上,我們不是使用 line
與 action
方法來建立通知,而是使用 markdown
方法來指定要使用的 Markdown 樣板名稱。可傳入一組資料陣列給該方法的第二個引數來將資料提供給該樣板使用:
1/**2 * Get the mail representation of the notification.3 */4public function toMail(object $notifiable): MailMessage5{6 $url = url('/invoice/'.$this->invoice->id);78 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): MailMessage5{6 $url = url('/invoice/'.$this->invoice->id);78 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 Paid34Your invoice has been paid!56<x-mail::button :url="$url">7View Invoice8</x-mail::button>910Thanks,<br>11{{ config('app.name') }}12</x-mail::message>
1<x-mail::message>2# Invoice Paid34Your invoice has been paid!56<x-mail::button :url="$url">7View Invoice8</x-mail::button>910Thanks,<br>11{{ config('app.name') }}12</x-mail::message>
Button 元件
Button 元件用來轉譯一個置中的按鈕連結。這個元件接受兩個引數,一個是 url
網址,另一個則是可選的 color
顏色。支援的顏色有 primary
、green
、red
。在通知中可以加上不限數量的 Button 元件:
1<x-mail::button :url="$url" color="green">2View Invoice3</x-mail::button>
1<x-mail::button :url="$url" color="green">2View Invoice3</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 指令來安裝 laravel-mail
素材標籤:
1php artisan vendor:publish --tag=laravel-mail
1php artisan vendor:publish --tag=laravel-mail
這個指令會將 Markdown 郵件元件安裝到 resources/views/vendor/mail
目錄下。mail
目錄會包含 html
與 text
目錄,這些目錄中包含了所有可用元件對應的呈現方式。可以隨意自訂這些元件。
自訂 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): MailMessage5{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): MailMessage5{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:table23php artisan migrate
1php artisan notifications:table23php artisan migrate
若 Notifiable Model 使用 UUID 或 ULID 的主索引鍵,請在 Notification 資料表的 Migration 中將 morphs
方法改成 uuidMorphs
或 ulidMorphs
。
格式化資料庫通知
若要讓某個通知支援保存在資料表中,請在該 Notification 類別上定義一個 toDatabase
或 toArray
方法。這個方法會收到一個 $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): array7{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): array7{8 return [9 'invoice_id' => $this->invoice->id,10 'amount' => $this->invoice->amount,11 ];12}
toDatabase
Vs. toArray
broadcast
通道也會使用 toArray
方法來判斷要將哪些資料廣播到 JavaScript 驅動的前端上。若想讓 database
與 broadcast
通道有不同的陣列呈現,請不要定義 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);23foreach ($user->notifications as $notification) {4 echo $notification->type;5}
1$user = App\Models\User::find(1);23foreach ($user->notifications as $notification) {4 echo $notification->type;5}
若只想取得「未讀」的通知,可使用 unreadNotifications
關聯。一樣,這些通知都會使用 created_at
來排序,最新的通知會在 Collection 的最前面:
1$user = App\Models\User::find(1);23foreach ($user->unreadNotifications as $notification) {4 echo $notification->type;5}
1$user = App\Models\User::find(1);23foreach ($user->unreadNotifications as $notification) {4 echo $notification->type;5}
若要在 JavaScript 用戶端中存取通知,請定義一個用來為 Notifiable 實體 (如:目前使用者) 回傳通知的 Notification Controller。接著就可以從 JavaScript 用戶端上建立一個 HTTP Request 來連線到該 Controller 的網址。
將通知標記為已讀
使用者檢視過通知後,我們通常會想將這些通知設為「已讀」。Illuminate\Notifications\Notifiable
Trait 提供了一個 markAsRead
方法,該方法會更新通知資料庫記錄上的 read_at
欄位:
1$user = App\Models\User::find(1);23foreach ($user->unreadNotifications as $notification) {4 $notification->markAsRead();5}
1$user = App\Models\User::find(1);23foreach ($user->unreadNotifications as $notification) {4 $notification->markAsRead();5}
不過,我們不需要在每個通知上迴圈,可以直接在一組通知的 Collection 上使用 markAsRead
方法:
1$user->unreadNotifications->markAsRead();
1$user->unreadNotifications->markAsRead();
也可以使用批次更新查詢來將所有的通知都列為已讀,而不需要先從資料庫中取出這些通知:
1$user = App\Models\User::find(1);23$user->unreadNotifications()->update(['read_at' => now()]);
1$user = App\Models\User::find(1);23$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;23/**4 * Get the broadcastable representation of the notification.5 */6public function toBroadcast(object $notifiable): BroadcastMessage7{8 return new BroadcastMessage([9 'invoice_id' => $this->invoice->id,10 'amount' => $this->invoice->amount,11 ]);12}
1use Illuminate\Notifications\Messages\BroadcastMessage;23/**4 * Get the broadcastable representation of the notification.5 */6public function toBroadcast(object $notifiable): BroadcastMessage7{8 return new BroadcastMessage([9 'invoice_id' => $this->invoice->id,10 'amount' => $this->invoice->amount,11 ]);12}
廣播佇列設定
所有的廣播通知都會被放入佇列以供廣播。若想為要用來廣播的佇列連線或佇列名稱,可使用 BroadcastMessage
的 onConnection
方法與 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(): string5{6 return 'broadcast.message';7}
1/**2 * Get the type of the notification being broadcast.3 */4public function broadcastType(): string5{6 return 'broadcast.message';7}
監聽通知
通知會在使用 {notifiable}.{id}
這種命名慣例命名的私有頻道上廣播。所以,假設我們要將通知傳送給一個 ID 為 1
的 App\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<?php23namespace App\Models;45use Illuminate\Broadcasting\PrivateChannel;6use Illuminate\Foundation\Auth\User as Authenticatable;7use Illuminate\Notifications\Notifiable;89class User extends Authenticatable10{11 use Notifiable;1213 /**14 * The channels the user receives notification broadcasts on.15 */16 public function receivesBroadcastNotificationsOn(): string17 {18 return 'users.'.$this->id;19 }20}
1<?php23namespace App\Models;45use Illuminate\Broadcasting\PrivateChannel;6use Illuminate\Foundation\Auth\User as Authenticatable;7use Illuminate\Notifications\Notifiable;89class User extends Authenticatable10{11 use Notifiable;1213 /**14 * The channels the user receives notification broadcasts on.15 */16 public function receivesBroadcastNotificationsOn(): string17 {18 return 'users.'.$this->id;19 }20}
簡訊通知
前置要求
Laravel 的簡訊通知傳送功能由 Vonage 驅動。(Vonage 前身為 Nexmo)。在使用 Vonage 傳送通知前,需要先安裝 laravel/vonage-notification-channel
與 guzzlehttp/guzzle
Composer 套件::
1composer require laravel/vonage-notification-channel guzzlehttp/guzzle
1composer require laravel/vonage-notification-channel guzzlehttp/guzzle
該套件中包含了其專屬的設定檔。不過,並不需要將該設定檔安裝到專案中也可以使用該套件。只要將 VONAGE_KEY
與 VONAGE_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;23/**4 * Get the Vonage / SMS representation of the notification.5 */6public function toVonage(object $notifiable): VonageMessage7{8 return (new VonageMessage)9 ->content('Your SMS message content');10}
1use Illuminate\Notifications\Messages\VonageMessage;23/**4 * Get the Vonage / SMS representation of the notification.5 */6public function toVonage(object $notifiable): VonageMessage7{8 return (new VonageMessage)9 ->content('Your SMS message content');10}
Unicode 內容
若有要傳送包含 Unicode 字元的簡訊,請在建立 VonageMessage
實體時呼叫 unicode
方法:
1use Illuminate\Notifications\Messages\VonageMessage;23/**4 * Get the Vonage / SMS representation of the notification.5 */6public function toVonage(object $notifiable): VonageMessage7{8 return (new VonageMessage)9 ->content('Your unicode message')10 ->unicode();11}
1use Illuminate\Notifications\Messages\VonageMessage;23/**4 * Get the Vonage / SMS representation of the notification.5 */6public function toVonage(object $notifiable): VonageMessage7{8 return (new VonageMessage)9 ->content('Your unicode message')10 ->unicode();11}
自訂「寄件」號碼
若想使用與 VONAGE_SMS_FROM
環境變數所設定之不同的號碼來傳送通知,可在 VonageMessage
實體上呼叫 from
方法:
1use Illuminate\Notifications\Messages\VonageMessage;23/**4 * Get the Vonage / SMS representation of the notification.5 */6public function toVonage(object $notifiable): VonageMessage7{8 return (new VonageMessage)9 ->content('Your SMS message content')10 ->from('15554443333');11}
1use Illuminate\Notifications\Messages\VonageMessage;23/**4 * Get the Vonage / SMS representation of the notification.5 */6public function toVonage(object $notifiable): VonageMessage7{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;23/**4 * Get the Vonage / SMS representation of the notification.5 */6public function toVonage(object $notifiable): VonageMessage7{8 return (new VonageMessage)9 ->clientReference((string) $notifiable->id)10 ->content('Your SMS message content');11}
1use Illuminate\Notifications\Messages\VonageMessage;23/**4 * Get the Vonage / SMS representation of the notification.5 */6public function toVonage(object $notifiable): VonageMessage7{8 return (new VonageMessage)9 ->clientReference((string) $notifiable->id)10 ->content('Your SMS message content');11}
為簡訊通知路由
若要將簡訊通知路由到正確的手機號碼,請在 Notifiable 實體上定義一個 routeNotificationForVonage
方法:
1<?php23namespace App\Models;45use Illuminate\Foundation\Auth\User as Authenticatable;6use Illuminate\Notifications\Notifiable;7use Illuminate\Notifications\Notification;89class User extends Authenticatable10{11 use Notifiable;1213 /**14 * Route notifications for the Vonage channel.15 */16 public function routeNotificationForVonage(Notification $notification): string17 {18 return $this->phone_number;19 }20}
1<?php23namespace App\Models;45use Illuminate\Foundation\Auth\User as Authenticatable;6use Illuminate\Notifications\Notifiable;7use Illuminate\Notifications\Notification;89class User extends Authenticatable10{11 use Notifiable;1213 /**14 * Route notifications for the Vonage channel.15 */16 public function routeNotificationForVonage(Notification $notification): string17 {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。
若只需要在該 Slack App 所在的工作空間內傳送通知,請確保該 Slack App 有 chat:write
、chat:write.public
,與 chat:write.customize
Scope。可以在 Slack 中 App Management 的「OAuth & Permissions」分頁內新增這些 Scope。
接著,請將該 App 的「Bot User OAuth Token」複製下來,並將其放在專案 services.php
設定檔中的 slack
設定陣列中。該 Token 可在 Slack 的「OAuth & Permissions」分頁中找到:
1'slack' => [2 'notifications' => [3 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),4 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),5 ],6],
1'slack' => [2 'notifications' => [3 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),4 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),5 ],6],
發佈 App
若你需要將通知傳送到由網站內其他使用者管理的 Slack 工作空間,則需要通過 Slack 來「發佈 (Distribute)」你的 App。在 Slack 中,可以在 App 內的「Manage Distribution」裡管理 App 的發佈。發佈 App 後,可以使用 Socialite 來以網站使用者的名義取得 Slack Bot 的 Token。
格式化 Slack 通知
若要讓某個 Notification 能支援以 Slack 訊息傳送,請在該 Notification 類別內定義 toSlack
方法。該方法會收到一個 $notifiable
實體,並應回傳 Illuminate\Notifications\Slack\SlackMessage
實體。可以使用 Slack 的 Block Kit API 來建立 Rich Notification。可以在 Slack 的 Block Kit Builder 內預覽下列範例:
1use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock;2use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock;3use Illuminate\Notifications\Slack\BlockKit\Composites\ConfirmObject;4use Illuminate\Notifications\Slack\SlackMessage;56/**7 * Get the Slack representation of the notification.8 */9public function toSlack(object $notifiable): SlackMessage10{11 return (new SlackMessage)12 ->text('One of your invoices has been paid!')13 ->headerBlock('Invoice Paid')14 ->contextBlock(function (ContextBlock $block) {15 $block->text('Customer #1234');16 })17 ->sectionBlock(function (SectionBlock $block) {18 $block->text('An invoice has been paid.');19 $block->field("*Invoice No:*\n1000")->markdown();21 })22 ->dividerBlock()23 ->sectionBlock(function (SectionBlock $block) {24 $block->text('Congratulations!');25 });26}
1use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock;2use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock;3use Illuminate\Notifications\Slack\BlockKit\Composites\ConfirmObject;4use Illuminate\Notifications\Slack\SlackMessage;56/**7 * Get the Slack representation of the notification.8 */9public function toSlack(object $notifiable): SlackMessage10{11 return (new SlackMessage)12 ->text('One of your invoices has been paid!')13 ->headerBlock('Invoice Paid')14 ->contextBlock(function (ContextBlock $block) {15 $block->text('Customer #1234');16 })17 ->sectionBlock(function (SectionBlock $block) {18 $block->text('An invoice has been paid.');19 $block->field("*Invoice No:*\n1000")->markdown();21 })22 ->dividerBlock()23 ->sectionBlock(function (SectionBlock $block) {24 $block->text('Congratulations!');25 });26}
Slack 可互動性
Slack 的 Block Kit 通知系統提供了強大的功能,可處理使用者的互動。若要使用這些功能,需要先在 Slack App 中啟用「Interactivity」,並將「Request URL」設定到一個由你的專案所管理的 URL。可以在 Slack 內的「Interactivity & Shortcuts」App 管理分頁中找到這些設定。
下列範例使用了 actionsBlock
方法。Slack 會向你設定的「Request URL」傳送一個 POST
要求,該要求中會包含點擊按鈕的 Slack 使用者 ID 等資訊。你的 App 可以依照此資訊來判斷要進行什麼動作。你也可以驗證該 Request 是否來自 Slack:
1use Illuminate\Notifications\Slack\BlockKit\Blocks\ActionsBlock;2use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock;3use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock;4use Illuminate\Notifications\Slack\SlackMessage;56/**7 * Get the Slack representation of the notification.8 */9public function toSlack(object $notifiable): SlackMessage10{11 return (new SlackMessage)12 ->text('One of your invoices has been paid!')13 ->headerBlock('Invoice Paid')14 ->contextBlock(function (ContextBlock $block) {15 $block->text('Customer #1234');16 })17 ->sectionBlock(function (SectionBlock $block) {18 $block->text('An invoice has been paid.');19 })20 ->actionsBlock(function (ActionsBlock $block) {21 // ID 預設為 "button_acknowledge_invoice"...22 $block->button('Acknowledge Invoice')->primary();2324 // 手動設定 ID...25 $block->button('Deny')->danger()->id('deny_invoice');26 });27}
1use Illuminate\Notifications\Slack\BlockKit\Blocks\ActionsBlock;2use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock;3use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock;4use Illuminate\Notifications\Slack\SlackMessage;56/**7 * Get the Slack representation of the notification.8 */9public function toSlack(object $notifiable): SlackMessage10{11 return (new SlackMessage)12 ->text('One of your invoices has been paid!')13 ->headerBlock('Invoice Paid')14 ->contextBlock(function (ContextBlock $block) {15 $block->text('Customer #1234');16 })17 ->sectionBlock(function (SectionBlock $block) {18 $block->text('An invoice has been paid.');19 })20 ->actionsBlock(function (ActionsBlock $block) {21 // ID 預設為 "button_acknowledge_invoice"...22 $block->button('Acknowledge Invoice')->primary();2324 // 手動設定 ID...25 $block->button('Deny')->danger()->id('deny_invoice');26 });27}
確認用 Modal (強制回應視窗)
若要讓使用者在執行動作前進行確認,可在定義按鈕時呼叫 confirm
方法。confirm
方法接受一個訊息,以及一個會收到 ConfirmObject
實體的 Closure:
1use Illuminate\Notifications\Slack\BlockKit\Blocks\ActionsBlock;2use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock;3use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock;4use Illuminate\Notifications\Slack\BlockKit\Composites\ConfirmObject;5use Illuminate\Notifications\Slack\SlackMessage;67/**8 * Get the Slack representation of the notification.9 */10public function toSlack(object $notifiable): SlackMessage11{12 return (new SlackMessage)13 ->text('One of your invoices has been paid!')14 ->headerBlock('Invoice Paid')15 ->contextBlock(function (ContextBlock $block) {16 $block->text('Customer #1234');17 })18 ->sectionBlock(function (SectionBlock $block) {19 $block->text('An invoice has been paid.');20 })21 ->actionsBlock(function (ActionsBlock $block) {22 $block->button('Acknowledge Invoice')23 ->primary()24 ->confirm(25 'Acknowledge the payment and send a thank you email?',26 function (ConfirmObject $dialog) {27 $dialog->confirm('Yes');28 $dialog->deny('No');29 }30 );31 });32}
1use Illuminate\Notifications\Slack\BlockKit\Blocks\ActionsBlock;2use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock;3use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock;4use Illuminate\Notifications\Slack\BlockKit\Composites\ConfirmObject;5use Illuminate\Notifications\Slack\SlackMessage;67/**8 * Get the Slack representation of the notification.9 */10public function toSlack(object $notifiable): SlackMessage11{12 return (new SlackMessage)13 ->text('One of your invoices has been paid!')14 ->headerBlock('Invoice Paid')15 ->contextBlock(function (ContextBlock $block) {16 $block->text('Customer #1234');17 })18 ->sectionBlock(function (SectionBlock $block) {19 $block->text('An invoice has been paid.');20 })21 ->actionsBlock(function (ActionsBlock $block) {22 $block->button('Acknowledge Invoice')23 ->primary()24 ->confirm(25 'Acknowledge the payment and send a thank you email?',26 function (ConfirmObject $dialog) {27 $dialog->confirm('Yes');28 $dialog->deny('No');29 }30 );31 });32}
檢查 Slack Block
若想檢查正在製作的 Block,可在 SlackMessage
實體上呼叫 dd
方法。dd
方法會傾印一個 Block Kit Builder 的網址,可用來在瀏覽器中預覽通知的 Payload。可傳入 true
給 dd
方法來傾印原始 Payload:
1return (new SlackMessage)2 ->text('One of your invoices has been paid!')3 ->headerBlock('Invoice Paid')4 ->dd();
1return (new SlackMessage)2 ->text('One of your invoices has been paid!')3 ->headerBlock('Invoice Paid')4 ->dd();
為 Slack 通知路由
若要將 Slack 通知導向給指定的 Slack 團隊或頻道,請在 Notifiable Model 中定義一個 routeNotificationForSlack
方法。該方法可回傳以下三種值:
-
null
- 會將通知的路由延期到 Notification 本身上設定。可以在 Notification 中定義SlackMessage
時使用to
方法來設定。 - 代表要傳送通知的 Slack 頻道字串,如
#support-channel
。 -
SlackRoute
實體,以便指定 OAuth 權杖與頻道名稱,如SlackRoute::make($this->slack_channel, $this->slack_token)
。該方法可用來將通知傳送給外部工作空間。
舉例來說,若在 routeNotificationForSlack
中回傳 #support-channel
,則會將通知傳送給該專案 services.php
設定檔中 Bot 使用者 OAuth 權杖所關聯工作空間中的 #support-channel
:
1<?php23namespace App\Models;45use Illuminate\Foundation\Auth\User as Authenticatable;6use Illuminate\Notifications\Notifiable;7use Illuminate\Notifications\Notification;89class User extends Authenticatable10{11 use Notifiable;1213 /**14 * Route notifications for the Slack channel.15 */16 public function routeNotificationForSlack(Notification $notification): mixed17 {18 return '#support-channel';19 }20}
1<?php23namespace App\Models;45use Illuminate\Foundation\Auth\User as Authenticatable;6use Illuminate\Notifications\Notifiable;7use Illuminate\Notifications\Notification;89class User extends Authenticatable10{11 use Notifiable;1213 /**14 * Route notifications for the Slack channel.15 */16 public function routeNotificationForSlack(Notification $notification): mixed17 {18 return '#support-channel';19 }20}
通知外部 Slack 工作空間
在傳送通知給外部 Slack 工作空間前,必須先發佈 (Distribute) 你的 Slack App。
當然,我們通常都想將通知傳送給 App 使用者所在的 Slack 工作空間。為此,我們需要先取得該使用者的 Slack OAuth 權杖。方便的是,Laravel Socialite 中譯包含了一個 Slack Driver,能讓你輕鬆地使用 Slack 來登入 App 使用者,並取得 Bot 權杖。
取得 Bot 權杖並將其保存在 App 的資料庫後,就可以使用 SlackRoute::make
方法來定義將通知導向使用者所在工作空間的 Route。此外,你的 App 也能提供使用者自訂通知傳送頻道的功能:
1<?php23namespace App\Models;45use Illuminate\Foundation\Auth\User as Authenticatable;6use Illuminate\Notifications\Notifiable;7use Illuminate\Notifications\Notification;8use Illuminate\Notifications\Slack\SlackRoute;910class User extends Authenticatable11{12 use Notifiable;1314 /**15 * Route notifications for the Slack channel.16 */17 public function routeNotificationForSlack(Notification $notification): mixed18 {19 return SlackRoute::make($this->slack_channel, $this->slack_token);20 }21}
1<?php23namespace App\Models;45use Illuminate\Foundation\Auth\User as Authenticatable;6use Illuminate\Notifications\Notifiable;7use Illuminate\Notifications\Notification;8use Illuminate\Notifications\Slack\SlackRoute;910class User extends Authenticatable11{12 use Notifiable;1314 /**15 * Route notifications for the Slack channel.16 */17 public function routeNotificationForSlack(Notification $notification): mixed18 {19 return SlackRoute::make($this->slack_channel, $this->slack_token);20 }21}
本土化通知
在 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;23class User extends Model implements HasLocalePreference4{5 /**6 * Get the user's preferred locale.7 */8 public function preferredLocale(): string9 {10 return $this->locale;11 }12}
1use Illuminate\Contracts\Translation\HasLocalePreference;23class User extends Model implements HasLocalePreference4{5 /**6 * Get the user's preferred locale.7 */8 public function preferredLocale(): string9 {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<?php23namespace Tests\Feature;45use App\Notifications\OrderShipped;6use Illuminate\Support\Facades\Notification;7use Tests\TestCase;89class ExampleTest extends TestCase10{11 public function test_orders_can_be_shipped(): void12 {13 Notification::fake();1415 // 處理訂單出貨...1617 // 判斷未有 Notification 被送出...18 Notification::assertNothingSent();1920 // 判斷某個 Notification 是否被傳送至給定的使用者...21 Notification::assertSentTo(22 [$user], OrderShipped::class23 );2425 // 判斷某個 Notification 是否未被送出...26 Notification::assertNotSentTo(27 [$user], AnotherNotification::class28 );2930 // 測試送出了給定數量的 Notification...31 Notification::assertCount(3);32 }33}
1<?php23namespace Tests\Feature;45use App\Notifications\OrderShipped;6use Illuminate\Support\Facades\Notification;7use Tests\TestCase;89class ExampleTest extends TestCase10{11 public function test_orders_can_be_shipped(): void12 {13 Notification::fake();1415 // 處理訂單出貨...1617 // 判斷未有 Notification 被送出...18 Notification::assertNothingSent();1920 // 判斷某個 Notification 是否被傳送至給定的使用者...21 Notification::assertSentTo(22 [$user], OrderShipped::class23 );2425 // 判斷某個 Notification 是否未被送出...26 Notification::assertNotSentTo(27 [$user], AnotherNotification::class28 );2930 // 測試送出了給定數量的 Notification...31 Notification::assertCount(3);32 }33}
可以傳入一個閉包給 assertSentTo
或 assertNotSentTo
方法,來判斷某個 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;34/**5 * The event listener mappings for the application.6 *7 * @var array8 */9protected $listen = [10 NotificationSending::class => [11 CheckNotificationStatus::class,12 ],13];
1use App\Listeners\CheckNotificationStatus;2use Illuminate\Notifications\Events\NotificationSending;34/**5 * The event listener mappings for the application.6 *7 * @var array8 */9protected $listen = [10 NotificationSending::class => [11 CheckNotificationStatus::class,12 ],13];
若 NotificationSending
事件的任一監聽程式中 handle
方法回傳 false
,就不會傳送該通知:
1use Illuminate\Notifications\Events\NotificationSending;23/**4 * Handle the event.5 */6public function handle(NotificationSending $event): bool7{8 return false;9}
1use Illuminate\Notifications\Events\NotificationSending;23/**4 * Handle the event.5 */6public function handle(NotificationSending $event): bool7{8 return false;9}
在 Event Listener 中,可以在該 Event 上存取 notifiable
、notification
、channel
等屬性,以取得更多有關通知收件人或通知本身的資訊:
1/**2 * Handle the event.3 */4public function handle(NotificationSending $event): void5{6 // $event->channel7 // $event->notifiable8 // $event->notification9}
1/**2 * Handle the event.3 */4public function handle(NotificationSending $event): void5{6 // $event->channel7 // $event->notifiable8 // $event->notification9}
已傳送事件 - NotificationSent
在傳送通知時,通知系統會分派一個 Illuminate\Notifications\Events\NotificationSent
事件。該 Event 中包含了一個「Notifiable」實體,以及通知實體本身。可以在專案的 EventServiceProvider
中為該 Event 註冊 Listener:
1use App\Listeners\LogNotification;2use Illuminate\Notifications\Events\NotificationSent;34/**5 * The event listener mappings for the application.6 *7 * @var array8 */9protected $listen = [10 NotificationSent::class => [11 LogNotification::class,12 ],13];
1use App\Listeners\LogNotification;2use Illuminate\Notifications\Events\NotificationSent;34/**5 * The event listener mappings for the application.6 *7 * @var array8 */9protected $listen = [10 NotificationSent::class => [11 LogNotification::class,12 ],13];
在 EventServiceProvider
中註冊好 Listener 後,可使用 event:generate
Artisan 指令來快速產生 Listener 類別。
在 Event Listener 中,可以在該 Event 上存取 notifiable
、notification
、channel
、response
等屬性,以取得更多有關通知收件人或通知本身的資訊:
1/**2 * Handle the event.3 */4public function handle(NotificationSent $event): void5{6 // $event->channel7 // $event->notifiable8 // $event->notification9 // $event->response10}
1/**2 * Handle the event.3 */4public function handle(NotificationSent $event): void5{6 // $event->channel7 // $event->notifiable8 // $event->notification9 // $event->response10}
自訂通道
Laravel 中隨附了許多通知通道,不過,我們也可以自行撰寫自訂的 Driver 來使用其他通道傳送通知。在 Laravel 中,要製作自訂 Driver 非常簡單。要開始製作自訂 Driver,請先定義一個包含 send
方法的類別。該方法應接收兩個引數:$notifiable
與 $notification
。
在 send
方法中,我們可以呼叫 Notification 上的方法,以取得這個頻道能理解的訊息物件,然後再將通知傳送給 $notifiable
實體:
1<?php23namespace App\Notifications;45use Illuminate\Notifications\Notification;67class VoiceChannel8{9 /**10 * Send the given notification.11 */12 public function send(object $notifiable, Notification $notification): void13 {14 $message = $notification->toVoice($notifiable);1516 // 傳送通知給 $notifiable 實體...17 }18}
1<?php23namespace App\Notifications;45use Illuminate\Notifications\Notification;67class VoiceChannel8{9 /**10 * Send the given notification.11 */12 public function send(object $notifiable, Notification $notification): void13 {14 $message = $notification->toVoice($notifiable);1516 // 傳送通知給 $notifiable 實體...17 }18}
定義好通知通道後,接著就可以在任何 Notification 類別內的 via
方法中回傳我們自訂 Driver 的類別名稱。在這個範例中,Notification 的 toVoice
方法可以回傳任何要用來代表語音訊息的物件。舉例來說,我們可以定義一個自訂的 VoiceMessage
類別來代表這些訊息:
1<?php23namespace App\Notifications;45use App\Notifications\Messages\VoiceMessage;6use App\Notifications\VoiceChannel;7use Illuminate\Bus\Queueable;8use Illuminate\Contracts\Queue\ShouldQueue;9use Illuminate\Notifications\Notification;1011class InvoicePaid extends Notification12{13 use Queueable;1415 /**16 * Get the notification channels.17 */18 public function via(object $notifiable): string19 {20 return VoiceChannel::class;21 }2223 /**24 * Get the voice representation of the notification.25 */26 public function toVoice(object $notifiable): VoiceMessage27 {28 // ...29 }30}
1<?php23namespace App\Notifications;45use App\Notifications\Messages\VoiceMessage;6use App\Notifications\VoiceChannel;7use Illuminate\Bus\Queueable;8use Illuminate\Contracts\Queue\ShouldQueue;9use Illuminate\Notifications\Notification;1011class InvoicePaid extends Notification12{13 use Queueable;1415 /**16 * Get the notification channels.17 */18 public function via(object $notifiable): string19 {20 return VoiceChannel::class;21 }2223 /**24 * Get the voice representation of the notification.25 */26 public function toVoice(object $notifiable): VoiceMessage27 {28 // ...29 }30}