Skip to content

通知 (Notifications) ​

簡介 ​

除了支援發送電子郵件外,Laravel 還支援透過各種傳送通道發送通知,包括電子郵件、簡訊(透過 Vonage,前身為 Nexmo)以及 Slack。此外,社群也建立了一系列社群打造的通知通道,讓您可以透過數十種不同的通道來發送通知!通知也可以儲存在資料庫中,以便能在網頁介面中顯示。

通常,通知應該是簡短的資訊訊息,用來通知使用者應用程式中發生的某些事情。例如,如果您正在撰寫一個帳務應用程式,您可能會透過電子郵件和簡訊通道向使用者發送「發票已付款」的通知。

建立通知 ​

在 Laravel 中,每個通知都由一個類別來表示,通常儲存在 app/Notifications 目錄中。如果在應用程式中沒看到這個目錄請不用擔心——當您執行 make:notification Artisan 指令時,系統會自動為您建立該目錄:

shell
php artisan make:notification InvoicePaid

這個指令會在您的 app/Notifications 目錄中放置一個全新的通知類別。每個通知類別都包含一個 via 方法以及數量可變的訊息建立方法,例如 toMail 或 toDatabase,這些方法會將通知轉換為專為該特定通道量身打造的訊息。

發送通知 ​

使用 Notifiable Trait ​

發送通知有兩種方式:使用 Notifiable trait 的 notify 方法,或是使用 Notification facade。預設情況下,Notifiable trait 已經包含在應用程式的 App\Models\User 模型中:

php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;
}

此 trait 所提供的 notify 方法需要接收一個通知實例:

php
use App\Notifications\InvoicePaid;

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

📌 備註

請記住,您可以在任何模型上使用 Notifiable trait。並不局限於只能包含在 User 模型中。

使用 Notification Facade ​

或者,您也可以透過 Notification facade 發送通知。當您需要向多個可接收通知的實體(例如使用者集合)發送通知時,這種方法非常有用。若要使用 Facade 發送通知,請將所有可接收通知的實體與通知實例傳遞給 send 方法:

php
use Illuminate\Support\Facades\Notification;

Notification::send($users, new InvoicePaid($invoice));

您也可以使用 sendNow 方法立即發送通知。即使通知實作了 ShouldQueue 介面,此方法也會立即發送通知:

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

指定傳送通道 ​

每個通知類別都有一個 via 方法,用來決定通知將透過哪些通道傳送。通知可以透過 mail、database、broadcast、vonage 及 slack 通道發送。

📌 備註

如果您想使用其他的傳送通道,例如 Telegram 或 Pusher,請參考由社群維護的 Laravel Notification Channels 網站。

via 方法會接收一個 $notifiable 實例,該實例為接收通知的類別實例。您可以使用 $notifiable 來決定通知應該透過哪些通道傳送:

php
/**
 * Get the notification's delivery channels.
 *
 * @return array<int, string>
 */
public function via(object $notifiable): array
{
    return $notifiable->prefers_sms ? ['vonage'] : ['mail', 'database'];
}

佇列通知 ​

⚠️ 警告

在將通知放入佇列之前,你應該先設定好佇列並啟動 Worker。

發送通知可能需要花費一些時間,特別是當通道需要呼叫外部 API 來遞送通知時。為了提高應用程式的回應速度,你可以透過在類別中加入 ShouldQueue 介面與 Queueable Trait,將通知加入佇列。使用 make:notification 指令產生的所有通知都已經預先匯入了該介面與 Trait,因此你可以直接將它們新增至你的通知類別中:

php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

class InvoicePaid extends Notification implements ShouldQueue
{
    use Queueable;

    // ...
}

一旦將 ShouldQueue 介面新增至通知後,你就可以像往常一樣發送通知。Laravel 會偵測到類別上的 ShouldQueue 介面,並自動將通知的遞送作業放入佇列:

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

將通知放入佇列時,系統會為每個收件者與通道的組合建立一個佇列任務。例如,如果你的通知有 3 個收件者與 2 個通道,系統將會派遣 6 個任務到佇列中。

延遲通知 ​

如果你想要延遲通知的發送,可以在實例化通知時鏈結呼叫 delay 方法:

php
$delay = now()->plus(minutes: 10);

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

你可以傳送一個陣列給 delay 方法,以指定特定通道的延遲時間:

php
$user->notify((new InvoicePaid($invoice))->delay([
    'mail' => now()->plus(minutes: 5),
    'sms' => now()->plus(minutes: 10),
]));

或者,你也可以在通知類別本身定義 withDelay 方法。withDelay 方法應該回傳一個包含通道名稱與延遲時間值的陣列:

php
/**
 * Determine the notification's delivery delay.
 *
 * @return array<string, \Illuminate\Support\Carbon>
 */
public function withDelay(object $notifiable): array
{
    return [
        'mail' => now()->plus(minutes: 5),
        'sms' => now()->plus(minutes: 10),
    ];
}

自訂通知佇列連線 ​

預設情況下,佇列通知會使用應用程式預設的佇列連線排入佇列。如果你想為特定通知指定不同的連線,可以在通知的建構子中呼叫 onConnection 方法:

php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

class InvoicePaid extends Notification implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new notification instance.
     */
    public function __construct()
    {
        $this->onConnection('redis');
    }
}

或者,如果你想為該通知支援的每個通知通道指定特定的佇列連線,可以在通知中定義 viaConnections 方法。該方法應該回傳一個由通道名稱 / 佇列連線名稱對應組成的陣列:

php
/**
 * Determine which connections should be used for each notification channel.
 *
 * @return array<string, string>
 */
public function viaConnections(): array
{
    return [
        'mail' => 'redis',
        'database' => 'sync',
    ];
}

自訂通知通道佇列 ​

如果你想為該通知支援的每個通知通道指定特定的佇列,可以在通知中定義 viaQueues 方法。該方法應該回傳一個由通道名稱 / 佇列名稱對應組成的陣列:

php
/**
 * Determine which queues should be used for each notification channel.
 *
 * @return array<string, string>
 */
public function viaQueues(): array
{
    return [
        'mail' => 'mail-queue',
        'slack' => 'slack-queue',
    ];
}

自訂佇列通知任務屬性 ​

你可以透過在通知類別上定義佇列屬性,來自訂底層佇列任務的行為。發送通知的佇列任務將會繼承這些屬性:

php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
use Illuminate\Queue\Attributes\FailOnTimeout;
use Illuminate\Queue\Attributes\MaxExceptions;
use Illuminate\Queue\Attributes\Timeout;
use Illuminate\Queue\Attributes\Tries;

#[Tries(5)]
#[Timeout(120)]
#[MaxExceptions(3)]
#[FailOnTimeout]
class InvoicePaid extends Notification implements ShouldQueue
{
    use Queueable;

    // ...
}

如果你想透過加密來確保佇列通知資料的隱私與完整性,請將 ShouldBeEncrypted 介面新增至你的通知類別:

php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

class InvoicePaid extends Notification implements ShouldQueue, ShouldBeEncrypted
{
    use Queueable;

    // ...
}

除了直接在通知類別上定義這些屬性外,你還可以定義 backoff 與 retryUntil 方法,以指定佇列通知任務的退避策略與重試逾時時間:

php
use DateTime;

/**
 * Calculate the number of seconds to wait before retrying the notification.
 */
public function backoff(): int
{
    return 3;
}

/**
 * Determine the time at which the notification should timeout.
 */
public function retryUntil(): DateTime
{
    return now()->plus(minutes: 5);
}

📌 備註

關於這些任務屬性與方法的更多資訊,請參考 佇列任務 的相關文件。

佇列通知中介層 ​

佇列通知可以像佇列任務一樣定義中介層。首先,請在你的通知類別上定義 middleware 方法。middleware 方法會接收 $notifiable 與 $channel 變數,讓你能夠根據通知的目的地自訂回傳的中介層:

php
use Illuminate\Queue\Middleware\RateLimited;

/**
 * Get the middleware the notification job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(object $notifiable, string $channel)
{
    return match ($channel) {
        'mail' => [new RateLimited('postmark')],
        'slack' => [new RateLimited('slack')],
        default => [],
    };
}

佇列通知與資料庫交易 ​

當佇列通知在資料庫交易內被派遣時,它們可能會在資料庫交易提交之前就被佇列處理。發生這種情況時,你在資料庫交易期間對 Model 或資料庫紀錄所做的任何更新可能尚未反映在資料庫中。此外,在交易內建立的任何 Model 或資料庫紀錄可能還不存在於資料庫中。如果你的通知依賴這些 Model,在處理發送佇列通知的任務時可能會發生意外的錯誤。

如果你的佇列連線設定選項 after_commit 設定為 false,你仍可以在發送通知時呼叫 afterCommit 方法,指定特定的佇列通知應該在所有未結的資料庫交易提交後才派遣:

php
use App\Notifications\InvoicePaid;

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

或者,你也可以從通知的建構子中呼叫 afterCommit 方法:

php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

class InvoicePaid extends Notification implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new notification instance.
     */
    public function __construct()
    {
        $this->afterCommit();
    }
}

📌 備註

欲了解更多解決這些問題的方法,請參考 佇列任務與資料庫交易 的相關文件。

判斷佇列通知是否應該發送 ​

當佇列通知被派遣到佇列中進行背景處理後,通常會由佇列 Worker 接收並發送給預期的收件者。

但是,如果你想在佇列 Worker 處理佇列通知時,由你做最終決定是否應該發送該通知,可以在通知類別上定義 shouldSend 方法。如果此方法回傳 false,則不會發送該通知:

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

發送通知之後 ​

如果你想在發送通知後執行特定的程式碼,可以在通知類別上定義 afterSending 方法。此方法將會接收可通知的實體、通道名稱以及來自該通道的回應:

php
/**
 * Handle the notification after it has been sent.
 */
public function afterSending(object $notifiable, string $channel, mixed $response): void
{
    // ...
}

隨選通知 ​

有時候,您可能需要將通知傳送給並未儲存為應用程式「使用者」的人。使用 Notification Facade 的 route 方法,您可以在傳送通知前指定臨時的通知路由資訊:

php
use Illuminate\Broadcasting\Channel;
use Illuminate\Support\Facades\Notification;

Notification::route('mail', '[email protected]')
    ->route('vonage', '5555555555')
    ->route('slack', '#slack-channel')
    ->route('broadcast', [new Channel('channel-name')])
    ->notify(new InvoicePaid($invoice));

若您想在傳送隨選通知至 mail 路由時提供收件者的姓名,可以傳入一個陣列,其中以 Email 地址作為鍵 (Key),並將姓名作為該陣列第一個元素的值:

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

使用 routes 方法,您可以一次為多個通知通道提供臨時路由資訊:

php
Notification::routes([
    'mail' => ['[email protected]' => 'Barrett Blair'],
    'vonage' => '5555555555',
])->notify(new InvoicePaid($invoice));

郵件通知 ​

格式化郵件訊息 ​

如果通知支援以電子郵件發送,你應該在通知類別中定義一個 toMail 方法。這個方法會接收一個 $notifiable 實體,並應回傳一個 Illuminate\Notifications\Messages\MailMessage 實體。

MailMessage 類別包含一些簡單的方法,可協助你建構交易式電子郵件訊息。郵件訊息可以包含文字行以及「行動呼籲 (Call to Action)」。讓我們看看一個 toMail 方法的範例:

php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    $url = url('/invoice/'.$this->invoice->id);

    return (new MailMessage)
        ->greeting('Hello!')
        ->line('One of your invoices has been paid!')
        ->lineIf($this->amount > 0, "Amount paid: {$this->amount}")
        ->action('View Invoice', $url)
        ->line('Thank you for using our application!');
}

📌 備註

請注意,我們在 toMail 方法中使用了 $this->invoice->id。你可以將通知生成訊息所需的所有資料傳遞給該通知的建構子。

在這個範例中,我們註冊了一行問候語、一行文字、一個行動呼籲按鈕,然後是另一行文字。由 MailMessage 物件提供的這些方法讓格式化小型交易式郵件變得既簡單又快速。郵件通道接著會將這些訊息元件轉換為美觀、響應式的 HTML 郵件範本,並附帶純文字對應版本。以下是由 mail 通道生成的電子郵件範例:

📌 備註

發送郵件通知時,請務必在 config/app.php 設定檔中設定 name 設定選項。此值將用於郵件通知訊息的頁首和頁尾。

錯誤訊息 ​

某些通知是用來告知使用者錯誤,例如發票付款失敗。你可以在建構訊息時呼叫 error 方法,以指出該郵件訊息是關於錯誤的。在郵件訊息上使用 error 方法時,行動呼籲按鈕將會是紅色而非黑色:

php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->error()
        ->subject('Invoice Payment Failed')
        ->line('...');
}

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

除了在通知類別中定義文字「行」外,你還可以使用 view 方法來指定應用於渲染通知郵件的自訂範本:

php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)->view(
        'mail.invoice.paid', ['invoice' => $this->invoice]
    );
}

你可以透過將檢視名稱作為傳給 view 方法的陣列第二個元素,來為郵件訊息指定純文字檢視:

php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)->view(
        ['mail.invoice.paid', 'mail.invoice.paid-text'],
        ['invoice' => $this->invoice]
    );
}

或者,如果你的訊息只有純文字檢視,你可以使用 text 方法:

php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)->text(
        'mail.invoice.paid-text', ['invoice' => $this->invoice]
    );
}

自訂寄件者 ​

預設情況下,電子郵件的寄件者 / 發件者地址定義在 config/mail.php 設定檔中。不過,你可以使用 from 方法為特定通知指定發件者地址:

php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->from('[email protected]', 'Barrett Blair')
        ->line('...');
}

自訂收件者 ​

當透過 mail 通道發送通知時,通知系統會自動在你的可通知實體上尋找 email 屬性。你可以透過在可通知實體上定義 routeNotificationForMail 方法,來自訂用於傳送通知的電子郵件地址:

php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * Route notifications for the mail channel.
     *
     * @return  array<string, string>|string
     */
    public function routeNotificationForMail(Notification $notification): array|string
    {
        // Return email address only...
        return $this->email_address;

        // Return email address and name...
        return [$this->email_address => $this->name];
    }
}

自訂郵件主旨 ​

預設情況下,電子郵件的主旨是格式化為「標題大寫 (Title Case)」的通知類別名稱。因此,如果你的通知類別名稱為 InvoicePaid,電子郵件的主旨將會是 Invoice Paid。如果你想為訊息指定不同的主旨,可以在建構訊息時呼叫 subject 方法:

php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->subject('Notification Subject')
        ->line('...');
}

自訂 Mailer ​

預設情況下,郵件通知將使用 config/mail.php 設定檔中定義的預設 mailer 發送。但是,你可以在執行期呼叫建構訊息時的 mailer 方法來指定不同的 mailer:

php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->mailer('postmark')
        ->line('...');
}

自訂範本 ​

你可以透過發布通知套件的資源來修改郵件通知所使用的 HTML 與純文字範本。執行此命令後,郵件通知範本將位於 resources/views/vendor/notifications 目錄中:

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

附件 ​

若要將附件新增至電子郵件通知中,可以在建構訊息時使用 attach 方法。attach 方法的第一個引數接受檔案的絕對路徑:

php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->greeting('Hello!')
        ->attach('/path/to/file');
}

📌 備註

通知郵件訊息提供的 attach 方法也接受可附加物件 (attachable objects)。請參閱完整的可附加物件文件以瞭解更多資訊。

當附加檔案至訊息時,您也可以傳入一個 array 作為 attach 方法的第二個引數,來指定顯示名稱及/或 MIME 型別:

php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->greeting('Hello!')
        ->attach('/path/to/file', [
            'as' => 'name.pdf',
            'mime' => 'application/pdf',
        ]);
}

必要時,可以使用 attachMany 方法將多個檔案附加至訊息中:

php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->greeting('Hello!')
        ->attachMany([
            '/path/to/forge.svg',
            '/path/to/vapor.svg' => [
                'as' => 'Logo.svg',
                'mime' => 'image/svg+xml',
            ],
        ]);
}

您可以使用 attachFromStorageDisk 方法附加儲存在特定檔案系統磁碟 (filesystem disk) 上的檔案。該方法接受磁碟名稱以及檔案在該磁碟上的路徑:

php
use App\Mail\InvoicePaid as InvoicePaidMailable;

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): Mailable
{
    return (new InvoicePaidMailable($this->invoice))
        ->to($notifiable->email)
        ->attachFromStorageDisk('s3', '/path/to/file', 'invoice.pdf', [
            'mime' => 'application/pdf',
        ]);
}

原始資料附件 ​

attachData 方法可用於將原始的位元組字串作為附件附加。呼叫 attachData 方法時,您應該提供指定給該附件的檔名:

php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->greeting('Hello!')
        ->attachData($this->pdf, 'name.pdf', [
            'mime' => 'application/pdf',
        ]);
}

新增標籤與詮釋資料 ​

某些第三方電子郵件提供者(如 Mailgun 與 Postmark)支援訊息的「標籤 (tags)」與「詮釋資料 (metadata)」,可用於對應用程式發送的電子郵件進行分組與追蹤。您可以透過 tag 和 metadata 方法將標籤與詮釋資料新增至電子郵件訊息中:

php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->greeting('Comment Upvoted!')
        ->tag('upvote')
        ->metadata('comment_id', $this->comment->id);
}

若您的應用程式使用的是 Mailgun 驅動程式,您可以參閱 Mailgun 的文件以瞭解更多關於 tags 與 metadata 的資訊。同樣地,也可以參閱 Postmark 的文件以取得關於其支援 tags 與 metadata 的更多資訊。

若您的應用程式使用 Amazon SES 來發送電子郵件,您應該使用 metadata 方法將 SES "tags" 附加至訊息。

自訂 Symfony 訊息 ​

MailMessage 類別的 withSymfonyMessage 方法允許您註冊一個閉包 (closure),該閉包會在發送訊息前傳入 Symfony Message 實例並被呼叫。這讓您有機會在訊息傳送前進行深度的自訂:

php
use Symfony\Component\Mime\Email;

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->withSymfonyMessage(function (Email $message) {
            $message->getHeaders()->addTextHeader(
                'Custom-Header', 'Header Value'
            );
        });
}

使用 Mailables ​

必要時,您可以從通知的 toMail 方法回傳一個完整的 mailable 物件。當回傳 Mailable 而非 MailMessage 時,您需要使用 mailable 物件的 to 方法來指定訊息收件者:

php
use App\Mail\InvoicePaid as InvoicePaidMailable;
use Illuminate\Mail\Mailable;

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): Mailable
{
    return (new InvoicePaidMailable($this->invoice))
        ->to($notifiable->email);
}

Mailables 與隨選通知 ​

若您正在發送隨選通知,傳遞給 toMail 方法的 $notifiable 實例將會是 Illuminate\Notifications\AnonymousNotifiable 的實例,它提供了一個 routeNotificationFor 方法,可用於取得隨選通知應該發送至的電子郵件地址:

php
use App\Mail\InvoicePaid as InvoicePaidMailable;
use Illuminate\Notifications\AnonymousNotifiable;
use Illuminate\Mail\Mailable;

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): Mailable
{
    $address = $notifiable instanceof AnonymousNotifiable
        ? $notifiable->routeNotificationFor('mail')
        : $notifiable->email;

    return (new InvoicePaidMailable($this->invoice))
        ->to($address);
}

預覽郵件通知 ​

設計郵件通知範本時,能像一般的 Blade 範本一樣在瀏覽器中快速預覽渲染後的郵件訊息是非常方便的。因此,Laravel 允許您直接從路由閉包或控制器中回傳由郵件通知產生的任何郵件訊息。當回傳 MailMessage 時,它將會被渲染並顯示在瀏覽器中,讓您可以快速預覽其設計,而無需將其發送到實際的電子郵件地址:

php
use App\Models\Invoice;
use App\Notifications\InvoicePaid;

Route::get('/notification', function () {
    $invoice = Invoice::find(1);

    return (new InvoicePaid($invoice))
        ->toMail($invoice->user);
});

Markdown 郵件通知 ​

Markdown 郵件通知讓你既能利用郵件通知的預置範本,又能更自由地撰寫更長、更具客製化的訊息。因為訊息是以 Markdown 撰寫,Laravel 能夠為訊息算繪出美觀且響應式的 HTML 範本,同時也會自動產生純文字版本。

建立訊息 ​

若要建立帶有對應 Markdown 範本的通知,你可以使用 make:notification Artisan 命令的 --markdown 選項:

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

如同所有其他郵件通知,使用 Markdown 範本的通知也應該在其通知類別中定義 toMail 方法。不過,你可以使用 markdown 方法來指定要使用的 Markdown 範本名稱,而不是使用 line 與 action 方法來構建通知。你希望傳遞給範本使用的資料陣列可以作為該方法的第二個引數傳入:

php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    $url = url('/invoice/'.$this->invoice->id);

    return (new MailMessage)
        ->subject('Invoice Paid')
        ->markdown('mail.invoice.paid', ['url' => $url]);
}

撰寫訊息 ​

Markdown 郵件通知結合了 Blade 元件與 Markdown 語法,讓你能夠輕鬆構建通知,同時發揮 Laravel 預先打造好的通知元件優勢:

blade
<x-mail::message>
# Invoice Paid

Your invoice has been paid!

<x-mail::button :url="$url">
View Invoice
</x-mail::button>

Thanks,<br>
{{ config('app.name') }}
</x-mail::message>

📌 備註

撰寫 Markdown 郵件時,請勿使用多餘的縮排。根據 Markdown 標準,Markdown 解析器會將縮排的內容算繪為程式碼區塊。

按鈕元件 ​

按鈕元件可算繪出置中的按鈕連結。該元件接收兩個引數:url 與可選的 color。支援的顏色有 primary、green 和 red。你可以根據需求在通知中新增任意數量的按鈕元件:

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

面板元件 ​

面板元件會將指定的文字區塊算繪在背景顏色與通知其他部分略有不同的面板中。這能讓你凸顯特定的文字區塊:

blade
<x-mail::panel>
This is the panel content.
</x-mail::panel>

表格元件 ​

表格元件讓你能夠將 Markdown 表格轉換為 HTML 表格。該元件接受 Markdown 表格作為其內容。支援使用預設的 Markdown 表格對齊語法來對齊表格欄位:

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

自訂元件 ​

你可以將所有 Markdown 通知元件匯出至你自己的應用程式中進行自訂。若要匯出元件,請使用 vendor:publish Artisan 命令來發布 laravel-mail 靜態資源標籤:

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

此命令會將 Markdown 郵件元件發布至 resources/views/vendor/mail 目錄。mail 目錄下會包含 html 與 text 目錄,分別代表每個可用元件的 HTML 與純文字樣式。你可以隨心所欲地自訂這些元件。

自訂 CSS ​

匯出元件後,resources/views/vendor/mail/html/themes 目錄下會包含一個 default.css 檔案。你可以自訂該檔案中的 CSS,而你的樣式將會自動以行內樣式 (In-line) 注入到 Markdown 通知的 HTML 呈現中。

如果你想為 Laravel 的 Markdown 元件建立一個全新的主題,可以在 html/themes 目錄中放置一個 CSS 檔案。命名並儲存你的 CSS 檔案後,更新 mail 設定檔中的 theme 選項,使其與你的新主題名稱一致。

若要為單一通知自訂主題,可在建立該通知的郵件訊息時呼叫 theme 方法。theme 方法接受發送通知時應使用的主題名稱:

php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->theme('invoice')
        ->subject('Invoice Paid')
        ->markdown('mail.invoice.paid', ['url' => $url]);
}

資料庫通知 ​

事前準備 ​

database 通知通道會將通知資訊儲存於資料庫資料表中。此資料表將包含通知類型以及描述通知內容的 JSON 資料結構等資訊。

您可以查詢此資料表,以在應用程式的使用者介面中顯示這些通知。但在這樣做之前,您需要先建立一個資料庫資料表來存放您的通知。您可以使用 make:notifications-table 命令來產生帶有適當資料表結構的遷移:

shell
php artisan make:notifications-table

php artisan migrate

📌 備註

若您的可通知模型正在使用 UUID 或 ULID 主鍵,您應該在通知資料表遷移中將 morphs 方法替換為 uuidMorphs 或 ulidMorphs。

格式化資料庫通知 ​

如果通知支援儲存在資料庫資料表中,您應該在通知類別上定義 toDatabase 或 toArray 方法。此方法將接收一個 $notifiable 實體,且應回傳一個純粹的 PHP 陣列。回傳的陣列將會被編碼為 JSON 並儲存在 notifications 資料表的 data 欄位中。讓我們來看一個 toArray 方法的範例:

php
/**
 * Get the array representation of the notification.
 *
 * @return array<string, mixed>
 */
public function toArray(object $notifiable): array
{
    return [
        'invoice_id' => $this->invoice->id,
        'amount' => $this->invoice->amount,
    ];
}

當通知儲存到您應用程式的資料庫時,type 欄位預設會設為該通知的類別名稱,而 read_at 欄位則會是 null。不過,您可以透過在通知類別中定義 databaseType 與 initialDatabaseReadAtValue 方法來自訂此行為:

php
use Illuminate\Support\Carbon;

/**
 * Get the notification's database type.
 */
public function databaseType(object $notifiable): string
{
    return 'invoice-paid';
}

/**
 * Get the initial value for the "read_at" column.
 */
public function initialDatabaseReadAtValue(): ?Carbon
{
    return null;
}

toDatabase vs. toArray ​

toArray 方法也被 broadcast 通道用來決定要廣播哪些資料到由 JavaScript 驅動的前端。若您希望針對 database 與 broadcast 通道使用兩種不同的陣列表示方式,您應該定義 toDatabase 方法,而不是 toArray 方法。

存取通知 ​

當通知儲存在資料庫後,您需要一種方便的方法從可通知的實體中存取它們。Laravel 預設的 App\Models\User 模型中包含的 Illuminate\Notifications\Notifiable trait,提供了一個 notifications Eloquent 關聯,會回傳該實體的通知。若要取得通知,您可以像存取任何其他 Eloquent 關聯一樣存取此方法。預設情況下,通知會依據 created_at 時間戳記進行排序,最新的通知會排在集合的開頭:

php
$user = App\Models\User::find(1);

foreach ($user->notifications as $notification) {
    echo $notification->type;
}

如果您只想取得「未讀」的通知,可以使用 unreadNotifications 關聯。同樣地,這些通知會依據 created_at 時間戳記進行排序,最新的通知會排在集合的開頭:

php
$user = App\Models\User::find(1);

foreach ($user->unreadNotifications as $notification) {
    echo $notification->type;
}

如果您只想取得「已讀」的通知,可以使用 readNotifications 關聯:

php
$user = App\Models\User::find(1);

foreach ($user->readNotifications as $notification) {
    echo $notification->type;
}

📌 備註

若要從 JavaScript 客戶端存取您的通知,您應該在應用程式中定義一個通知控制器,負責回傳可通知實體(例如當前使用者)的通知。接著,您可以從 JavaScript 客戶端向該控制器的 URL 發送 HTTP 請求。

將通知標示為已讀 ​

通常當使用者檢視通知時,您會希望將通知標示為「已讀」。Illuminate\Notifications\Notifiable trait 提供了 markAsRead 方法,該方法會更新通知資料庫紀錄中的 read_at 欄位:

php
$user = App\Models\User::find(1);

foreach ($user->unreadNotifications as $notification) {
    $notification->markAsRead();
}

然而,您可以直接在通知集合上使用 markAsRead 方法,而不需要走訪循環每一個通知:

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

您也可以使用批次更新查詢將所有通知標示為已讀,而無需將它們從資料庫中取出:

php
$user = App\Models\User::find(1);

$user->unreadNotifications()->update(['read_at' => now()]);

您可以使用 delete 刪除通知,將它們從資料表中完全移除:

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

廣播通知 ​

事前準備 ​

在廣播通知之前,你應該先設定並熟悉 Laravel 的事件廣播服務。事件廣播提供了一種從 JavaScript 驅動的前端回應 Laravel 伺服器端事件的方法。

格式化廣播通知 ​

broadcast 通道使用 Laravel 的事件廣播服務來廣播通知,讓你的 JavaScript 前端能夠即時接收通知。如果通知支援廣播,你可以在通知類別中定義 toBroadcast 方法。該方法會接收一個 $notifiable 實體,並應回傳一個 BroadcastMessage 實例。如果 toBroadcast 方法不存在,則會使用 toArray 方法來收集應該廣播的資料。回傳的資料將會被編碼為 JSON 並廣播到你的 JavaScript 前端。讓我們來看看 toBroadcast 方法的範例:

php
use Illuminate\Notifications\Messages\BroadcastMessage;

/**
 * Get the broadcastable representation of the notification.
 */
public function toBroadcast(object $notifiable): BroadcastMessage
{
    return new BroadcastMessage([
        'invoice_id' => $this->invoice->id,
        'amount' => $this->invoice->amount,
    ]);
}

廣播佇列設定 ​

所有廣播通知都會進入佇列進行廣播。如果你想設定用於將廣播操作排入佇列的佇列連線或佇列名稱,可以使用 BroadcastMessage 的 onConnection 與 onQueue 方法:

php
return (new BroadcastMessage($data))
    ->onConnection('sqs')
    ->onQueue('broadcasts');

自訂通知類型 ​

除了你指定的資料外,所有廣播通知還包含一個 type 欄位,其中含有該通知的完整類別名稱。如果你想自訂通知的 type,可以在通知類別中定義 broadcastType 方法:

php
/**
 * Get the type of the notification being broadcast.
 */
public function broadcastType(): string
{
    return 'broadcast.message';
}

監聽通知 ​

通知將會在遵循 {notifiable}.{id} 慣例格式的私有通道上廣播。因此,如果你將通知發送給 ID 為 1 的 App\Models\User 實例,該通知將會在 App.Models.User.1 私有通道上廣播。使用 Laravel Echo 時,你可以使用 notification 方法輕鬆監聽通道上的通知:

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

使用 React、Vue 或 Svelte ​

Laravel Echo 包含 React、Vue 與 Svelte 的 Hook,讓你能夠無痛地監聽通知。首先,呼叫用來監聽通知的 useEchoNotification Hook。當使用該 Hook 的元件卸載 (Unmounted) 時,useEchoNotification Hook 會自動離開通道:

js
import { useEchoNotification } from "@laravel/echo-react";

useEchoNotification(
    `App.Models.User.${userId}`,
    (notification) => {
        console.log(notification.type);
    },
);
vue
<script setup lang="ts">
import { useEchoNotification } from "@laravel/echo-vue";

useEchoNotification(
    `App.Models.User.${userId}`,
    (notification) => {
        console.log(notification.type);
    },
);
</script>
svelte
<script>
import { useEchoNotification } from "@laravel/echo-svelte";

useEchoNotification(
    `App.Models.User.${userId}`,
    (notification) => {
        console.log(notification.type);
    },
);
</script>

預設情況下,該 Hook 會監聽所有通知。若要指定你想監聽的通知類型,可以傳遞字串或類型陣列給 useEchoNotification:

js
import { useEchoNotification } from "@laravel/echo-react";

useEchoNotification(
    `App.Models.User.${userId}`,
    (notification) => {
        console.log(notification.type);
    },
    'App.Notifications.InvoicePaid',
);
vue
<script setup lang="ts">
import { useEchoNotification } from "@laravel/echo-vue";

useEchoNotification(
    `App.Models.User.${userId}`,
    (notification) => {
        console.log(notification.type);
    },
    'App.Notifications.InvoicePaid',
);
</script>
svelte
<script>
import { useEchoNotification } from "@laravel/echo-svelte";

useEchoNotification(
    `App.Models.User.${userId}`,
    (notification) => {
        console.log(notification.type);
    },
    'App.Notifications.InvoicePaid',
);
</script>

你也可以指定通知有效載荷 (Payload) 資料的結構,以提供更高的型別安全性與編輯便利性:

ts
type InvoicePaidNotification = {
    invoice_id: number;
    created_at: string;
};

useEchoNotification<InvoicePaidNotification>(
    `App.Models.User.${userId}`,
    (notification) => {
        console.log(notification.invoice_id);
        console.log(notification.created_at);
        console.log(notification.type);
    },
    'App.Notifications.InvoicePaid',
);

自訂通知通道 ​

如果你想自訂實體的廣播通知要落在哪個通道廣播,可以在可接收通知的實體上定義 receivesBroadcastNotificationsOn 方法:

php
<?php

namespace App\Models;

use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The channels the user receives notification broadcasts on.
     */
    public function receivesBroadcastNotificationsOn(): string
    {
        return 'users.'.$this->id;
    }
}

簡訊通知 ​

事前準備 ​

在 Laravel 中發送簡訊通知是由 Vonage(前身為 Nexmo)所支援。在透過 Vonage 發送通知之前,您需要安裝 laravel/vonage-notification-channel 與 guzzlehttp/guzzle 套件:

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

該套件包含一個設定檔。然而,您不需要將此設定檔匯出至您自己的應用程式中。您只需使用 VONAGE_KEY 與 VONAGE_SECRET 環境變數來定義您的 Vonage 金鑰與密鑰即可。

定義金鑰後,您應該設定 VONAGE_SMS_FROM 環境變數,用來定義預設發送簡訊訊息的電話號碼。您可以在 Vonage 控制台內產生此電話號碼:

ini
VONAGE_SMS_FROM=15556666666

格式化簡訊通知 ​

如果通知支援以簡訊發送,您應該在通知類別中定義 toVonage 方法。該方法將接收一個 $notifiable 實體,並應回傳一個 Illuminate\Notifications\Messages\VonageMessage 實例:

php
use Illuminate\Notifications\Messages\VonageMessage;

/**
 * Get the Vonage / SMS representation of the notification.
 */
public function toVonage(object $notifiable): VonageMessage
{
    return (new VonageMessage)
        ->content('Your SMS message content');
}

Unicode 內容 ​

如果您的簡訊訊息包含 Unicode 字元,您應該在建構 VonageMessage 實例時呼叫 unicode 方法:

php
use Illuminate\Notifications\Messages\VonageMessage;

/**
 * Get the Vonage / SMS representation of the notification.
 */
public function toVonage(object $notifiable): VonageMessage
{
    return (new VonageMessage)
        ->content('Your unicode message')
        ->unicode();
}

自訂「寄件者」號碼 ​

如果您想從不同於 VONAGE_SMS_FROM 環境變數所指定的電話號碼發送某些通知,您可以在 VonageMessage 實例上呼叫 from 方法:

php
use Illuminate\Notifications\Messages\VonageMessage;

/**
 * Get the Vonage / SMS representation of the notification.
 */
public function toVonage(object $notifiable): VonageMessage
{
    return (new VonageMessage)
        ->content('Your SMS message content')
        ->from('15554443333');
}

新增 Client Reference ​

如果您希望追蹤每個使用者、團隊或客戶的成本,您可以在通知中新增「客戶參考號 (client reference)」。Vonage 將允許您使用此客戶參考號來產生報表,以便您能更好地瞭解特定客戶的簡訊使用量。客戶參考號可以是長度不超過 40 個字元的任何字串:

php
use Illuminate\Notifications\Messages\VonageMessage;

/**
 * Get the Vonage / SMS representation of the notification.
 */
public function toVonage(object $notifiable): VonageMessage
{
    return (new VonageMessage)
        ->clientReference((string) $notifiable->id)
        ->content('Your SMS message content');
}

路由簡訊通知 ​

若要將 Vonage 通知路由至正確的電話號碼,請在可接收通知的實體上定義 routeNotificationForVonage 方法:

php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * Route notifications for the Vonage channel.
     */
    public function routeNotificationForVonage(Notification $notification): string
    {
        return $this->phone_number;
    }
}

Slack 通知 ​

事前準備 ​

在發送 Slack 通知之前,您應該透過 Composer 安裝 Slack 通知通道:

shell
composer require laravel/slack-notification-channel

此外,您必須為您的 Slack 工作空間建立一個 Slack App。

如果您只需要向建立該 App 的同一 Slack 工作空間發送通知,您應確保您的 App 擁有 chat:write、chat:write.public 以及 chat:write.customize 權限範圍 (Scopes)。這些權限範圍可以在 Slack 內的 "OAuth & Permissions" App 管理分頁中新增。

接著,複製 App 的 "Bot User OAuth Token",並將其放在您應用程式的 services.php 設定檔中的 slack 設定陣列內。此令牌可以在 Slack 內的 "OAuth & Permissions" 分頁中找到:

php
'slack' => [
    'notifications' => [
        'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
        'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
    ],
],

App 發布 ​

如果您的應用程式將發送通知給由使用者所擁有的外部 Slack 工作空間,您將需要透過 Slack 來「發布 (Distribute)」您的 App。App 的發布可以在 Slack 內的 App "Manage Distribution" 分頁中進行管理。當您的 App 發布後,您可以使用 Socialite 代表應用程式的使用者來取得 Slack Bot 令牌。

格式化 Slack 通知 ​

如果通知支援以 Slack 訊息發送,您應該在通知類別上定義一個 toSlack 方法。該方法將接收一個 $notifiable 實體,並應回傳一個 Illuminate\Notifications\Slack\SlackMessage 實例。您可以使用 Slack 的 Block Kit API 來建立豐富的通知內容。以下範例可在 Slack 的 Block Kit builder 中預覽:

php
use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock;
use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock;
use Illuminate\Notifications\Slack\SlackMessage;

/**
 * Get the Slack representation of the notification.
 */
public function toSlack(object $notifiable): SlackMessage
{
    return (new SlackMessage)
        ->text('One of your invoices has been paid!')
        ->headerBlock('Invoice Paid')
        ->contextBlock(function (ContextBlock $block) {
            $block->text('Customer #1234');
        })
        ->sectionBlock(function (SectionBlock $block) {
            $block->text('An invoice has been paid.');
            $block->field("*Invoice No:*\n1000")->markdown();
            $block->field("*Invoice Recipient:*\n[email protected]")->markdown();
        })
        ->dividerBlock()
        ->sectionBlock(function (SectionBlock $block) {
            $block->text('Congratulations!');
        });
}

使用 Slack 的 Block Kit Builder 範本 ​

除了使用流暢的訊息建立器方法來建構 Block Kit 訊息之外,您還可以將由 Slack Block Kit Builder 所產生的原始 JSON 負載 (Payload) 傳遞給 usingBlockKitTemplate 方法:

php
use Illuminate\Notifications\Slack\SlackMessage;
use Illuminate\Support\Str;

/**
 * Get the Slack representation of the notification.
 */
public function toSlack(object $notifiable): SlackMessage
{
    $template = <<<JSON
        {
          "blocks": [
            {
              "type": "header",
              "text": {
                "type": "plain_text",
                "text": "Team Announcement"
              }
            },
            {
              "type": "section",
              "text": {
                "type": "plain_text",
                "text": "We are hiring!"
              }
            }
          ]
        }
    JSON;

    return (new SlackMessage)
        ->usingBlockKitTemplate($template);
}

Slack 互動性 ​

Slack 的 Block Kit 通知系統提供了強大的功能來處理使用者互動。若要使用這些功能,您的 Slack App 必須啟用「Interactivity」,並設定一個指向由您的應用程式所提供之 URL 的「Request URL」。這些設定可以在 Slack 內的「Interactivity & Shortcuts」App 管理分頁中進行管理。

在以下使用 actionsBlock 方法的範例中,Slack 會傳送一個 POST 請求到您的「Request URL」,其 Payload 包含點擊按鈕的 Slack 使用者、點擊按鈕的 ID 等資訊。您的應用程式接著便能根據 Payload 決定要執行的動作。您也應該驗證請求確實是由 Slack 所發出的:

php
use Illuminate\Notifications\Slack\BlockKit\Blocks\ActionsBlock;
use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock;
use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock;
use Illuminate\Notifications\Slack\SlackMessage;

/**
 * Get the Slack representation of the notification.
 */
public function toSlack(object $notifiable): SlackMessage
{
    return (new SlackMessage)
        ->text('One of your invoices has been paid!')
        ->headerBlock('Invoice Paid')
        ->contextBlock(function (ContextBlock $block) {
            $block->text('Customer #1234');
        })
        ->sectionBlock(function (SectionBlock $block) {
            $block->text('An invoice has been paid.');
        })
        ->actionsBlock(function (ActionsBlock $block) {
             // ID defaults to "button_acknowledge_invoice"...
            $block->button('Acknowledge Invoice')->primary();

            // Manually configure the ID...
            $block->button('Deny')->danger()->id('deny_invoice');
        });
}

確認對話框 ​

如果您希望使用者在執行某個動作前必須進行確認,可以在定義按鈕時呼叫 confirm 方法。confirm 方法接收一個訊息以及一個接收 ConfirmObject 實例的 Closure:

php
use Illuminate\Notifications\Slack\BlockKit\Blocks\ActionsBlock;
use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock;
use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock;
use Illuminate\Notifications\Slack\BlockKit\Composites\ConfirmObject;
use Illuminate\Notifications\Slack\SlackMessage;

/**
 * Get the Slack representation of the notification.
 */
public function toSlack(object $notifiable): SlackMessage
{
    return (new SlackMessage)
        ->text('One of your invoices has been paid!')
        ->headerBlock('Invoice Paid')
        ->contextBlock(function (ContextBlock $block) {
            $block->text('Customer #1234');
        })
        ->sectionBlock(function (SectionBlock $block) {
            $block->text('An invoice has been paid.');
        })
        ->actionsBlock(function (ActionsBlock $block) {
            $block->button('Acknowledge Invoice')
                ->primary()
                ->confirm(
                    'Acknowledge the payment and send a thank you email?',
                    function (ConfirmObject $dialog) {
                        $dialog->confirm('Yes');
                        $dialog->deny('No');
                    }
                );
        });
}

檢視 Slack Blocks ​

如果您想快速檢視建構中的 Block,可以在 SlackMessage 實例上呼叫 dd 方法。dd 方法會產生並印出一個指向 Slack Block Kit Builder 的 URL,會在瀏覽器中顯示 Payload 與通知的預覽。您可以傳遞 true 給 dd 方法來印出原始 Payload:

php
return (new SlackMessage)
    ->text('One of your invoices has been paid!')
    ->headerBlock('Invoice Paid')
    ->dd();

路由 Slack 通知 ​

若要將 Slack 通知引導至適當的 Slack 團隊與頻道,請在可接收通知的 Model 上定義 routeNotificationForSlack 方法。此方法可以傳回以下三種值之一:

  • null - 延後路由決策,改為使用通知本身所設定的頻道。您可以在建構 SlackMessage 時使用 to 方法來在通知內設定頻道。
  • 指定要傳送通知之 Slack 頻道的字串,例如 #support-channel。
  • SlackRoute 實例 - 允許您指定 OAuth 令牌與頻道名稱,例如 SlackRoute::make($this->slack_channel, $this->slack_token)。此方法應用於向外部工作空間傳送通知。

例如,從 routeNotificationForSlack 方法傳回 #support-channel 會將通知傳送到與您應用程式 services.php 設定檔中的 Bot User OAuth 令牌相關聯之工作空間內的 #support-channel 頻道:

php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * Route notifications for the Slack channel.
     */
    public function routeNotificationForSlack(Notification $notification): mixed
    {
        return '#support-channel';
    }
}

通知外部 Slack 工作空間 ​

📌 備註

在向外部 Slack 工作空間傳送通知之前,您的 Slack App 必須先完成散佈 (Distributed)。

當然,您經常會需要傳送通知到您應用程式使用者所擁有的 Slack 工作空間。為此,您首先需要為該使用者取得 Slack OAuth 令牌。值得慶幸的是,Laravel Socialite 包含一個 Slack 驅動程式,可讓您輕鬆地使用 Slack 認證應用程式的使用者並取得 Bot 令牌。

一旦取得 Bot 令牌並將其儲存在應用程式的資料庫中,您就可以利用 SlackRoute::make 方法將通知路由至該使用者的工作空間。此外,您的應用程式可能也需要提供一個機會,讓使用者指定通知應該傳送到哪個頻道:

php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Slack\SlackRoute;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * Route notifications for the Slack channel.
     */
    public function routeNotificationForSlack(Notification $notification): mixed
    {
        return SlackRoute::make($this->slack_channel, $this->slack_token);
    }
}

通知在地化 ​

Laravel 允許您使用 HTTP 請求當前語系以外的其他語系發送通知,如果通知被推入佇列,甚至會記住此語系設定。

為此,Illuminate\Notifications\Notification 類別提供了一個 locale 方法來設定所需的語言。應用程式在解析該通知時會切換至該語系,解析完成後則會切換回原本的語系:

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

多個可接收通知實體的在地化也可以透過 Notification Facade 來達成:

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

使用者偏好語系 ​

有時,應用程式會儲存每個使用者的偏好語系。透過在可接收通知的模型上實作 HasLocalePreference 契約(Contracts),您可以指示 Laravel 在發送通知時使用此儲存的語系:

php
use Illuminate\Contracts\Translation\HasLocalePreference;

class User extends Model implements HasLocalePreference
{
    /**
     * Get the user's preferred locale.
     */
    public function preferredLocale(): string
    {
        return $this->locale;
    }
}

當您實作了該介面後,Laravel 在向該模型發送通知和 Mailables 時,將會自動使用其偏好的語系。因此,使用此介面時不需要再呼叫 locale 方法:

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

測試 ​

您可以使用 Notification Facade 的 fake 方法來防止發送通知。通常,發送通知與您實際測試的程式碼無關。極大的可能,只需單純斷言(Assert)Laravel 已收到發送給定通知的指示即可。

在呼叫 Notification Facade 的 fake 方法後,您就可以斷言已經指示將通知發送給使用者,甚至可以檢視通知接收到的資料:

php
<?php

use App\Notifications\OrderShipped;
use Illuminate\Support\Facades\Notification;

test('orders can be shipped', function () {
    Notification::fake();

    // Perform order shipping...

    // Assert that no notifications were sent...
    Notification::assertNothingSent();

    // Assert a notification was sent to the given users...
    Notification::assertSentTo(
        [$user], OrderShipped::class
    );

    // Assert a notification was not sent...
    Notification::assertNotSentTo(
        [$user], AnotherNotification::class
    );

    // Assert a notification was sent twice...
    Notification::assertSentTimes(WeeklyReminder::class, 2);

    // Assert that a given number of notifications were sent...
    Notification::assertCount(3);
});
php
<?php

namespace Tests\Feature;

use App\Notifications\OrderShipped;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_orders_can_be_shipped(): void
    {
        Notification::fake();

        // Perform order shipping...

        // Assert that no notifications were sent...
        Notification::assertNothingSent();

        // Assert a notification was sent to the given users...
        Notification::assertSentTo(
            [$user], OrderShipped::class
        );

        // Assert a notification was not sent...
        Notification::assertNotSentTo(
            [$user], AnotherNotification::class
        );

        // Assert a notification was sent twice...
        Notification::assertSentTimes(WeeklyReminder::class, 2);

        // Assert that a given number of notifications were sent...
        Notification::assertCount(3);
    }
}

您可以傳遞閉包 (Closure) 給 assertSentTo 或 assertNotSentTo 方法,以斷言發送的通知通過給定的「真值測試 (Truth test)」。如果至少發送了一則通過給定真值測試的通知,斷言就會成功:

php
Notification::assertSentTo(
    $user,
    function (OrderShipped $notification, array $channels) use ($order) {
        return $notification->order->id === $order->id;
    }
);

隨選通知 ​

如果您要測試的程式碼會發送隨選通知,您可以透過 assertSentOnDemand 方法來測試隨選通知是否已發送:

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

透過傳遞閉包作為 assertSentOnDemand 方法的第二個引數,您可以判斷隨選通知是否發送到正確的「路由」位址:

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

通知事件 ​

Notification Sending 事件 ​

當通知正在發送時,通知系統會分派 Illuminate\Notifications\Events\NotificationSending 事件。該事件包含「可接收通知 (Notifiable)」實體與通知實例本身。您可以為您應用程式中的此事件建立事件監聽器:

php
use Illuminate\Notifications\Events\NotificationSending;

class CheckNotificationStatus
{
    /**
     * Handle the event.
     */
    public function handle(NotificationSending $event): void
    {
        // ...
    }
}

若 NotificationSending 事件的事件監聽器在其 handle 方法中回傳 false,則該通知將不會被發送:

php
/**
 * Handle the event.
 */
public function handle(NotificationSending $event): bool
{
    return false;
}

在事件監聽器內,您可以存取事件上的 notifiable、notification 和 channel 屬性,以深入了解通知收件者或通知本身:

php
/**
 * Handle the event.
 */
public function handle(NotificationSending $event): void
{
    // $event->channel
    // $event->notifiable
    // $event->notification
}

Notification Sent 事件 ​

當通知發送完成時,通知系統會分派 Illuminate\Notifications\Events\NotificationSent 事件。該事件包含「可接收通知」實體與通知實例本身。您可以為您應用程式中的此事件建立事件監聽器:

php
use Illuminate\Notifications\Events\NotificationSent;

class LogNotification
{
    /**
     * Handle the event.
     */
    public function handle(NotificationSent $event): void
    {
        // ...
    }
}

在事件監聽器內,您可以存取事件上的 notifiable、notification、channel 和 response 屬性,以深入了解通知收件者或通知本身:

php
/**
 * Handle the event.
 */
public function handle(NotificationSent $event): void
{
    // $event->channel
    // $event->notifiable
    // $event->notification
    // $event->response
}

自訂通道 ​

Laravel 內建了一些通知通道,但您可能希望撰寫自己的驅動程式,透過其他通道來傳送通知。Laravel 讓這件事變得相當簡單。要開始使用,請定義一個包含 send 方法的類別。該方法應該接收兩個引數:$notifiable 與 $notification。

在 send 方法中,您可以呼叫通知上的方法來取得該通道可理解的訊息物件,然後以您希望的任何方式將通知傳送給 $notifiable 實例:

php
<?php

namespace App\Notifications;

use Illuminate\Notifications\Notification;

class VoiceChannel
{
    /**
     * Send the given notification.
     */
    public function send(object $notifiable, Notification $notification): void
    {
        $message = $notification->toVoice($notifiable);

        // Send notification to the $notifiable instance...
    }
}

當您的通知通道類別定義完成後,您就可以從任何通知的 via 方法中回傳該類別名稱。在這個範例中,通知的 toVoice 方法可以回傳任何您選擇用來代表語音訊息的物件。例如,您可以定義自己的 VoiceMessage 類別來代表這些訊息:

php
<?php

namespace App\Notifications;

use App\Notifications\Messages\VoiceMessage;
use App\Notifications\VoiceChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

class InvoicePaid extends Notification
{
    use Queueable;

    /**
     * Get the notification channels.
     */
    public function via(object $notifiable): string
    {
        return VoiceChannel::class;
    }

    /**
     * Get the voice representation of the notification.
     */
    public function toVoice(object $notifiable): VoiceMessage
    {
        // ...
    }
}