> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 通知（Notifications）

> 說明如何使用 Laravel 的通知系統，透過郵件、資料庫、Slack 等多種頻道進行一次性群發。

## 什麼是 Notification

Laravel 的通知（Notification）系統，是一套透過統一 API 將通知發送到郵件、SMS、Slack、資料庫等多種傳遞頻道的機制。

**Mail 與 Notification 的差異**

| 比較項目 | Mail          | Notification        |
| ---- | ------------- | ------------------- |
| 主要用途 | 內容豐富的 HTML 郵件 | 簡短的資訊通知             |
| 傳遞頻道 | 僅限郵件          | 郵件、DB、Slack、SMS 等多種 |
| 範本   | 完全自由          | 簡潔的訊息格式             |

像帳單付款完成通知這種需要同時透過多種頻道發送相同通知的情況，就適合使用 Notification。

## 建立 Notification 類別

使用 `make:notification` Artisan 指令來產生類別。

```shell theme={null}
php artisan make:notification InvoicePaid
```

產生的類別會放在 `app/Notifications/` 目錄下。類別中包含 `via()` 方法，以及對應各頻道的訊息產生方法。

## 通知的發送方式

### 使用 Notifiable Trait

`App\Models\User` 預設就已包含 `Notifiable` trait。可透過 `notify()` 方法發送通知。

```php theme={null}
use App\Notifications\InvoicePaid;

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

<Info>
  `Notifiable` trait 不限於 `User` 模型，可加入任何模型中使用。
</Info>

### 使用 Notification Facade

若要同時發送給多位使用者，可使用 `Notification` facade。

```php theme={null}
use Illuminate\Support\Facades\Notification;

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

若要立即發送（跳過佇列），請使用 `sendNow()`。

```php theme={null}
Notification::sendNow($developers, new DeploymentCompleted($deployment));
```

## 指定傳遞頻道

在 `via()` 方法中以陣列形式回傳所要使用的頻道。

```mermaid theme={null}
flowchart TD
    A["$user->notify(new InvoicePaid())"] --> B["透過 via()<br>決定頻道"]
    B --> C{"選擇頻道"}
    C --> D["mail<br>toMail()"]
    C --> E["database<br>toArray()"]
    C --> F["broadcast<br>toBroadcast()"]
    C --> G["vonage<br>toVonage()"]
    C --> H["slack<br>toSlack()"]
```

```php theme={null}
public function via(object $notifiable): array
{
    return ['mail', 'database'];
}
```

也可以依據使用者的偏好切換頻道。

```php theme={null}
public function via(object $notifiable): array
{
    return $notifiable->prefers_sms ? ['vonage'] : ['mail', 'database'];
}
```

主要可用的頻道如下。

| 頻道    | 鍵值          | 說明                       |
| ----- | ----------- | ------------------------ |
| 郵件    | `mail`      | 以郵件發送通知                  |
| 資料庫   | `database`  | 儲存至 DB 並顯示於 UI           |
| 廣播    | `broadcast` | 即時通知（WebSocket）          |
| SMS   | `vonage`    | 透過 Vonage（舊 Nexmo）發送 SMS |
| Slack | `slack`     | 發佈至 Slack 頻道             |

## 郵件頻道的通知

在 `toMail()` 方法中回傳 `MailMessage` 實體。

```php theme={null}
use Illuminate\Notifications\Messages\MailMessage;

public function toMail(object $notifiable): MailMessage
{
    $url = url('/invoice/'.$this->invoice->id);

    return (new MailMessage)
        ->greeting('您好！')
        ->line('我們已確認您帳單的付款。')
        ->action('查看帳單', $url)
        ->line('感謝您的使用。');
}
```

`MailMessage` 的主要方法如下。

| 方法           | 說明         |
| ------------ | ---------- |
| `greeting()` | 開頭問候語      |
| `line()`     | 本文的一行      |
| `action()`   | 按鈕連結       |
| `subject()`  | 主旨         |
| `from()`     | 寄件者位址      |
| `mailer()`   | 使用的 mailer |

若要通知錯誤，可加上 `error()` 方法，按鈕會變為紅色。

```php theme={null}
return (new MailMessage)
    ->error()
    ->subject('付款失敗')
    ->line('帳單的付款處理失敗。');
```

### Markdown 通知郵件

也可以使用 Markdown 格式的豐富郵件。使用 `--markdown` 選項來產生類別。

```shell theme={null}
php artisan make:notification InvoicePaid --markdown=mail.invoice.paid
```

以 `toMarkdownMail()` 取代 `toMail()`。

```php theme={null}
use Illuminate\Notifications\Messages\MailMessage;

public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->markdown('mail.invoice.paid', [
            'url' => url('/invoice/'.$this->invoice->id),
            'invoice' => $this->invoice,
        ]);
}
```

## 資料庫頻道的通知

使用資料庫頻道可將通知儲存至 DB，並顯示於應用程式的 UI 上。

### 準備資料表

首先建立 `notifications` 資料表。

```shell theme={null}
php artisan make:notifications-table

php artisan migrate
```

### 定義 toArray 方法

在 `toArray()` 方法中以陣列形式回傳要儲存的資料。

```php theme={null}
public function toArray(object $notifiable): array
{
    return [
        'invoice_id' => $this->invoice->id,
        'amount' => $this->invoice->amount,
        'message' => '我們已確認您帳單的付款。',
    ];
}
```

這些資料會以 JSON 格式儲存到 `notifications` 資料表的 `data` 欄位中。

### 取得通知

可透過 `Notifiable` trait 提供的 `notifications` 關聯來取得通知。

```php theme={null}
$user = App\Models\User::find(1);

foreach ($user->notifications as $notification) {
    echo $notification->type;
    echo $notification->data['message'];
}
```

若只想取得未讀通知，可使用 `unreadNotifications`。

```php theme={null}
foreach ($user->unreadNotifications as $notification) {
    echo $notification->data['message'];
}
```

### 標記為已讀

使用 `markAsRead()` 將通知標記為已讀。

```php theme={null}
// 逐一標記為已讀
foreach ($user->unreadNotifications as $notification) {
    $notification->markAsRead();
}

// 一次全部標記為已讀
$user->unreadNotifications->markAsRead();

// 以查詢批次更新
$user->unreadNotifications()->update(['read_at' => now()]);
```

## 通知的佇列處理

若發送通知需要較多時間，可加入 `ShouldQueue` 介面與 `Queueable` trait 改為佇列處理。透過 `make:notification` 產生的類別預設就已匯入這些內容。

```php theme={null}
<?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` 之後，只要呼叫 `notify()` 就會自動被推入佇列。

```php theme={null}
$user->notify(new InvoicePaid($invoice));
```

也可延遲發送。

```php theme={null}
$user->notify(
    (new InvoicePaid($invoice))->delay(now()->plus(minutes: 10))
);
```

## 同時發送至多個頻道

只要在 `via()` 方法中回傳多個頻道，並分別定義各自的方法，就能同時將相同的通知發送至多個頻道。

```php theme={null}
<?php

namespace App\Notifications;

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

class InvoicePaid extends Notification implements ShouldQueue
{
    use Queueable;

    public function __construct(
        private readonly Invoice $invoice,
    ) {}

    public function via(object $notifiable): array
    {
        // 同時發送至郵件與資料庫
        return ['mail', 'database'];
    }

    public function toMail(object $notifiable): MailMessage
    {
        return (new MailMessage)
            ->subject('已確認您帳單的付款')
            ->line('帳單 #'.$this->invoice->id.' 的付款已確認。')
            ->action('查看帳單', url('/invoice/'.$this->invoice->id));
    }

    public function toArray(object $notifiable): array
    {
        return [
            'invoice_id' => $this->invoice->id,
            'amount' => $this->invoice->amount,
        ];
    }
}
```

## 隨選通知

若想發送通知給在應用程式中沒有帳號的使用者，可使用 `Notification::route()`。

```php theme={null}
use Illuminate\Support\Facades\Notification;

Notification::route('mail', 'guest@example.com')
    ->route('vonage', '5555551212')
    ->notify(new InvoicePaid($invoice));
```

## 總結

| 想做的事            | 方式                                                 |
| --------------- | -------------------------------------------------- |
| 建立 Notification | `php artisan make:notification ClassName`          |
| 通知使用者           | `$user->notify(new MyNotification())`              |
| 通知多位使用者         | `Notification::send($users, new MyNotification())` |
| 以郵件發送           | 在 `via()` 回傳 `mail` 並定義 `toMail()`                 |
| 儲存至 DB          | 在 `via()` 回傳 `database` 並定義 `toArray()`            |
| 以佇列非同步發送        | 實作 `ShouldQueue`                                   |
| 發送至多個頻道         | 在 `via()` 回傳多個鍵值                                   |


## Related topics

- [通知頻道 - Laravel Bluesky](/zh-TW/packages/laravel-bluesky/notification.md)
- [Laravel Notification for Discord(Webhook)](/zh-TW/packages/laravel-notification-discord-webhook.md)
- [通知 Channel - LINE SDK for Laravel](/zh-TW/packages/laravel-line-sdk/notification.md)
- [BlueskyManager 與 HasShortHand](/zh-TW/packages/laravel-bluesky/bluesky-manager.md)
- [Laravel Console Starter](/zh-TW/packages/laravel-console-starter/index.md)
