> ## 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`  | 保存到数据库，在 UI 中展示        |
| 广播    | `broadcast` | 实时通知（WebSocket）        |
| SMS   | `vonage`    | 通过 Vonage（原 Nexmo）发送短信 |
| 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,
        ]);
}
```

## 数据库渠道通知

数据库渠道可以把通知保存到数据库，并在应用 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,
        ];
    }
}
```

## 按需通知（On-demand notifications）

要向没有账号的人发送通知，可以使用 `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()`                |
| 保存到数据库          | 在 `via()` 中返回 `database` 并定义 `toArray()`           |
| 使用队列异步发送        | 实现 `ShouldQueue`                                   |
| 发送到多个渠道         | 在 `via()` 中返回多个键                                   |


## Related topics

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