通知


介绍

除了支持发送电子邮件外,Laravel 还提供了支持通过各种传递渠道发送通知,包括电子邮件、SMS(通过Vonage,前身为 Nexmo)和 Slack。此外,社区还创建了各种通知渠道,可以通过数十个不同的渠道发送通知!通知也可以存储在数据库中,以便在 Web 接口中显示。

通常,通知应该是简短的信息性消息,通知用户发生在应用程序中的某些事情。例如,如果您编写的是一个计费应用程序,可以通过电子邮件和短信渠道向用户发送“发票已支付”通知。

生成通知

在 Laravel 中,每个通知都由一个单独的类表示,通常存储在 app/Notifications 目录中。如果您在应用程序中看不到此目录,不用担心 - 在运行 make:notification Artisan 命令时,系统会自动为您创建此目录:

php artisan make:notification InvoicePaid

该命令将在 app/Notifications 目录中放置一个新的通知类。每个通知类都包含一个 via 方法和若干个构建消息的方法,例如 toMailtoDatabase,这些方法将通知转换为适合特定渠道的消息。

发送通知

使用 Notifiable Trait

有两种方式可以发送通知:使用 Notifiable Trait 的 notify 方法或使用 Notification 门面Notifiable Trait 默认包含在应用程序的 App\Models\User 模型中:

<?php

namespace App\Models;

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

class User extends Authenticatable
{
    use Notifiable;
}

此 Trait 提供的 notify 方法期望接收一个通知实例:

use App\Notifications\InvoicePaid;
 
$user->notify(new InvoicePaid($invoice));

请记住,您可以在任何模型中使用 Notifiable Trait。您不仅限于将其包含在 User 模型中。

使用 Notification 门面

或者,您可以通过 Notification 门面发送通知。当您需要向多个可通知实体(例如一组用户)发送通知时,可以使用这种方法。要使用该门面发送通知,请将所有可通知实体和通知实例传递给 send 方法:

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

您也可以使用 sendNow 方法立即发送通知。即使通知实现了 ShouldQueue 接口,该方法也会立即发送通知:

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

指定传递渠道

每个通知类都有一个 via 方法,用于确定通知将通过哪些渠道传递。通知可以通过 maildatabasebroadcastvonageslack 渠道发送。

如果您想使用其他传递渠道,如 Telegram 或 Pusher,请查看由社区驱动的 Laravel 通知渠道网站

via 方法接收一个 $notifiable 实例,这将是要发送通知的类的实例。您可以使用 $notifiable 来确定通知应该通过哪些渠道传递:

/**
 * 获取通知的传递渠道
 *
 * @return array<int, string>
 */
public function via(object $notifiable): array
{
    return $notifiable->prefers_sms ? ['vonage'] : ['mail', 'database'];
}

排队通知

在将通知排队之前,您应该配置队列并启动工作进程

发送通知可能需要一些时间,尤其是如果通道需要调用外部 API 来传递通知。为了加快您的应用程序的响应时间,请通过将 ShouldQueue 接口和 Queueable Trait 添加到类中来让通知排队。使用 make:notification 命令生成的所有通知已经导入了该接口和 Trait,因此您可以立即将它们添加到通知类中:

<?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 接口,并自动排队发送通知:

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

在排队通知时,将为每个接收者和渠道组合创建一个排队作业。例如,如果您的通知有三个接收者和两个渠道,将会向队列派发六个作业。

延迟通知

如果您想要延迟发送通知,可以将延迟方法链接到通知实例的实例化中:

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

为特定渠道延迟通知

您可以将数组传递给延迟方法,以指定特定渠道的延迟时间:

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

或者,您可以在通知类本身上定义一个 withDelay 方法。withDelay 方法应返回一个包含渠道名称和延迟值的数组:

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

自定义通知队列连接

默认情况下,排队通知将使用应用程序的默认队列连接进行排队。如果您想为特定通知指定不同的连接,请在通知类上定义 $connection 属性:

/**
 * 在对通知排队时使用的队列连接名称
 *
 * @var string
 */
public $connection = 'redis';

或者,如果您想为通知支持的每个通知渠道指定特定的队列连接,请在通知上定义一个 viaConnections 方法。该方法应返回一个包含渠道名称和队列连接名称的数组:

/**
 * 确定每个通知渠道应使用哪些连接
 *
 * @return array<string, string>
 */
public function viaConnections(): array
{
    return [
        'mail' => 'redis',
        'database' => 'sync',
    ];
}

自定义通知渠道队列

如果您想为通知支持的每个通知渠道指定特定的队列,请在通知上定义一个 viaQueues 方法。该方法应返回一个包含渠道名称和队列名称的数组:

/**
 * 确定每个通知渠道应使用哪些队列
 *
 * @return array<string, string>
 */
public function viaQueues(): array
{
    return [
        'mail' => 'mail-queue',
        'slack' => 'slack-queue',
    ];
}

排队通知和数据库事务

当在数据库事务中派发排队通知时,它们可能会在数据库事务提交之前由队列处理。当发生这种情况时,在数据库事务期间对模型或数据库记录进行的任何更新可能尚未在数据库中反映出来。此外,事务中创建的任何模型或数据库记录可能不存在于数据库中。如果您的通知依赖于这些模型,当处理发送排队通知的作业时,可能会出现意外错误。

如果您的队列连接的 after_commit 配置选项设置为 false,您仍然可以在发送通知时调用 afterCommit 方法,以指示应在提交了所有打开的数据库事务之后派发特定的排队通知:

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

或者,您可以在通知的构造函数中调用 afterCommit 方法:

<?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();
    }
}

要了解有关解决这些问题的更多信息,请参阅有关排队作业和数据库事务的文档

确定是否应发送排队通知

将队列通知派发到后台处理队列后,它通常会被队列工作者接受并发送给预期的接收方。

但是,如果您想在队列工作者处理通知后对排队通知是否应该发送做出最后决定,可以在通知类上定义一个 shouldSend 方法。如果该方法返回 false,则不会发送通知:

/**
 * 确定是否应发送通知
 */
public function shouldSend(object $notifiable, string $channel): bool
{
    return $this->invoice->isPaid();
}

按需通知

有时,您可能需要向未存储为应用程序“用户”的某人发送通知。使用 Notification 门面的 route 方法,可以在发送通知之前指定点对点的通知路由信息:

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

Notification::route('mail', 'taylor@example.com')
            ->route('vonage', '5555555555')
            ->route('slack', 'https://hooks.slack.com/services/...')
            ->route('broadcast', [new Channel('channel-name')])
            ->notify(new InvoicePaid($invoice));

如果在发送按需通知到邮件路由时提供收件人的名称,可以提供一个包含邮件地址作为键和名称作为数组的第一个元素的数组:

Notification::route('mail', [
    'barrett@example.com' => 'Barrett Blair',
])->notify(new InvoicePaid($invoice));

邮件通知

格式化邮件消息

如果支持将通知发送为电子邮件,您应该在通知类中定义一个 toMail 方法。该方法将接收一个 $notifiable 实体,并应返回一个 Illuminate\Notifications\Messages\MailMessage 实例。

MailMessage 类包含一些简单的方法,可帮助您构建事务性电子邮件消息。邮件消息可以包含文本行以及“操作调用”。让我们来看一个 toMail 方法的示例:

/**
 * 获取通知的电子邮件表示
 */
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 邮件模板及其普通文本对应项。以下是邮件渠道生成的邮件示例:

img

在发送邮件通知时,请确保在 config/app.php 配置文件中设置了 name 配置选项。该值将用于邮件通知消息的头部和底部。

错误消息

某些通知会通知用户错误,例如失败的发票付款。在构建消息时,您可以调用 error 方法指示邮件消息是关于错误的。在邮件消息上使用 error 方法时,操作调用按钮的颜色将为红色而不是黑色:

/**
 * 获取通知的电子邮件表示
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->error()
                ->subject('Invoice Payment Failed')
                ->line('...');
}

其他邮件通知格式选项

您可以使用 view 方法而不是在通知类中定义文本“行”来指定用于呈现通知邮件的自定义模板:

/**
 * 获取通知的电子邮件表示
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)->view(
        'emails.name', ['invoice' => $this->invoice]
    );
}

您可以通过将视图名称作为传递给 view 方法的数组的第二个元素,指定邮件消息的纯文本视图:

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

自定义发件人

默认情况下,电子邮件的发件人/From 地址定义在 config/mail.php 配置文件中。但是,您可以使用 from 方法为特定通知指定特定的发件人:

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->from('barrett@example.com', 'Barrett Blair')
                ->line('...');
}

自定义收件人

当通过邮件渠道发送通知时,通知系统会自动检查您的可通知实体上是否有一个 email 属性。您可以通过为可通知实体上的 routeNotificationForMail 方法定义一个要使用的电子邮件地址来自定义将用于传递通知的电子邮件地址:

<?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;
    
    /**
     * 为邮件通道获取通知路由。
     *
     * @return  array<string, string>|string
     */
    public function routeNotificationForMail(Notification $notification): array|string
    {
        // 仅返回电子邮件地址...
        return $this->email_address;

        // 返回电子邮件地址和姓名...
        return [$this->email_address => $this->name];
    }
    
}

自定义主题

默认情况下,电子邮件的主题是通知类名称格式化为“Title Case”。“InvoicePaid”通知类的电子邮件主题将是“Invoice Paid”。如果要为消息指定不同的主题,可以在构建消息时调用 subject 方法:

/**
 * 获取通知的电子邮件表示
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->subject('Notification Subject')
                ->line('...');
}

自定义邮件程序

默认情况下,电子邮件通知将使用 config/mail.php 配置文件中定义的默认邮件程序发送。但是,您可以在构建消息时调用 mailer 方法以在运行时指定不同的邮件程序:

/**
 * 获取通知的电子邮件表示
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->mailer('postmark')
                ->line('...');
}

自定义模板

您可以通过发布通知包的资源来修改邮件通知使用的 HTML 和纯文本模板。运行以下命令后,邮件通知模板将位于 resources/views/vendor/notifications 目录下:

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

附件

您可以使用 attach 方法向电子邮件通知添加附件。attach 方法接受文件的绝对路径作为其第一个参数:

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

通知邮件消息的 attach 方法还接受可附加的对象。请查阅完整的 attachable object 文档以了解更多信息。

在附件方法中附加文件时,还可以通过将附件方法的第二个参数指定为像以下这样的数组来指定显示名称和/或 MIME 类型:

/**
 * 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',
                ]);
}

与在可存储磁盘上直接附加文件的方式不同,您应该使用带有文件在存储磁盘上的绝对路径的 attach 方法。或者,您可以从 toMail 方法返回一个 Mailable

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)
                ->attachFromStorage('/path/to/file');
}

当需要时,可以使用多个附件方法向消息添加多个文件:

/**
 * 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',
                    ],
                ]);
}

原始数据附件

attachData 方法可用于将原始字符串的字节作为附件附加。在调用 attachData 方法时,您应该提供应分配给附件的文件名:

/**
 * 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)支持消息“标签”和“元数据”,可用于分组和跟踪应用程序发送的电子邮件。您可以通过 tagmetadata 方法向邮件消息中添加标签和元数据:

/**
 * 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 的文档以了解有关标签元数据的更多信息。同样,也可以查阅 Postmark 文档,以了解更多关于标签元数据的支持信息。

如果您的应用程序使用 Amazon SES 发送电子邮件,您应该使用 metadata 方法将 SES “标签”附加到消息上。

自定义 Symfony 消息

MailMessage 类的 withSymfonyMessage 方法允许您在发送消息之前注册一个闭包,该闭包将在 Symfony Message 实例上调用。这样,您可以在传递消息之前对消息进行深度定制:

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 方法来指定消息的收件人:

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 方法,可以用于检索应将按需通知发送到的电子邮件地址:

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,则它将在浏览器中呈现和显示,允许您在不需要发送到实际电子邮件地址的情况下快速预览其设计:

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 选项:

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

与所有其他邮件通知一样,使用 Markdown 模板的通知应在其通知类上定义一个 toMail 方法。但是,不要使用 lineaction 方法构建通知,而是使用 markdown 方法指定应使用的 Markdown 模板的名称。可以将要提供给模板的数据数组作为方法的第二个参数传递:

/**
 * 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 预定义的通知组件的同时轻松构建通知:

<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>

按钮组件

按钮组件会渲染出一个居中的按钮链接。组件接受两个参数,一个 url 和一个可选的 color。支持的颜色值有 primarygreenred。您可以在通知中添加任意数量的按钮组件:

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

面板组件

面板组件会在通知中呈现一个区块文本,其背景色与通知的其余部分略有不同。这样可以让您引起对特定文本块的注意:

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

表格组件

表格组件允许您将 Markdown 表格转换为 HTML 表格。组件将 Markdown 表格作为其内容。通过使用默认的 Markdown 表格对齐语法,支持表格列对齐设置:

<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 资源标签发布到资源目录:

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

此命令将会将 Markdown 邮件组件发布到 resources/views/vendor/mail 目录。邮件目录将包含一个 html 和一个 text 目录,每个目录中包含其各自的可用组件的表示形式。您可以自由定制这些组件。

自定义 CSS

在导出了组件后,resources/views/vendor/mail/html/themes 目录中将包含一个 default.css 文件。您可以自定义此文件中的 CSS,并且您的样式将自动内联到 Markdown 通知的 HTML 表示中。

如果您想为 Laravel 的 Markdown 组件构建一个全新的主题,可以在 html/themes 目录中放置一个 CSS 文件。命名并保存您的 CSS 文件后,更新 mail 配置文件的 theme 选项以匹配新主题的名称。

要为单个通知自定义主题,可以调用主题方法,在发送通知时指定应使用的主题名称:

/**
 * 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]);
}

数据库通知

先决条件

数据库通知渠道会将通知信息存储在数据库表中。此表将包含诸如通知类型以及描述通知的 JSON 数据结构等信息。您可以查询该表以在应用程序的用户界面中显示通知。但是,在此之前,您需要创建一个数据库表来容纳通知。您可以使用 notifications:table 命令生成具有正确表结构的迁移

php artisan notifications:table

php artisan migrate

如果您的可通知模型使用 UUID 或 ULID 主键,则应将 notifications 表迁移中的 morphs 方法替换为 uuidMorphsulidMorphs

格式化数据库通知

如果通知支持存储在数据库表中,您应该在通知类中定义一个 toDatabasetoArray 方法。该方法将接收一个 $notifiable 实体,并应返回一个纯 PHP 数组。返回的数组将被编码为 JSON 并存储在 notifications 表的 data 列中。让我们看一个toArray 方法的示例:

/**
 * 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,
    ];
}

toDatabase 与 toArray

toArray 方法还用于广播通道,以确定要向 JavaScript 式前端广播的数据。如果希望数据库和广播通道具有两种不同的数组表示形式,应该定义 toDatabase 方法而不是 toArray 方法。

访问通知

通知存储在数据库中后,您需要方便地从可通知实体中访问它们。Illuminate\Notifications\Notifiable trait 包含在 Laravel 默认的 App\Models\User 模型中,其中包括一个返回该实体的通知的 notifications Eloquent 关系。要获取通知,可以像访问其他 Eloquent 关系一样访问此方法。默认情况下,通知将按照 created_at 时间戳进行排序,最近的通知位于集合的开头:

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

如果要仅检索“未读”通知,可以使用 unreadNotifications 关系。同样,这些通知将按照 created_at 时间戳进行排序,最近的通知位于集合的开头:

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

要使用 JavaScript 客户端从通知中访问通知,您应为应用程序定义一个通知控制器,该控制器返回给可通知实体的通知,例如当前用户。然后,您可以从 JavaScript 客户端向该控制器的 URL 发出 HTTP 请求。

将通知标记为已读

通常,您会在用户查看通知后将通知标记为“已读”。Illuminate\Notifications\Notifiable trait 提供了一个 markAsRead 方法,可以更新通知的数据库记录的 read_at 列:

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

只要通知支持标记为“已读”,您可以直接在通知集合上使用 markAsRead 方法:

$user->unreadNotifications->markAsRead();

您还可以使用批量更新查询,而无需从数据库中检索通知即可将所有通知标记为已读:

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

您可以通过删除通知将它们从表中完全删除:

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

广播通知

先决条件

在进行广播通知之前,您应该配置并熟悉 Laravel 的事件广播服务。事件广播提供了一种从 JavaScript 驱动的前端对服务器端 Laravel 事件作出反应的方式。

格式化广播通知

广播通道使用 Laravel 的事件广播服务进行广播,允许您的 JavaScript 驱动的前端实时捕获通知。如果一个通知支持广播,您可以在通知类上定义一个 toBroadcast 方法。此方法将接收一个 $notifiable 实体,并应返回一个 BroadcastMessage 实例。如果 toBroadcast 方法不存在,则将使用 toArray 方法来收集应该广播的数据。返回的数据将编码为 JSON 并广播到您的 JavaScript 驱动的前端。让我们看一个示例 toBroadcast 方法:

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,
    ]);
}

广播队列配置

所有广播通知都将排队进行广播。如果您想配置用于排队广播操作的队列连接或队列名称,可以使用BroadcastMessageonConnectiononQueue 方法:

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

自定义通知类型

除了您指定的数据之外,所有广播通知还包含一个 type 字段,其中包含通知的完整类名。如果您想自定义通知类型,可以在通知类上定义一个 broadcastType 方法:

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

监听通知

通知将以 {notifiable}.{id} 的格式在私有通道上进行广播。因此,如果您向一个 App\Models\User 实例发送一个 ID 为 1 的通知,则该通知将在 App.Models.User.1 私有通道上进行广播。当使用 Laravel Echo 时,您可以轻松地使用 notification 方法监听通道上的通知:

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

自定义通知通道

如果您想自定义实体的广播通知所广播到的通道,可以在可通知实体上定义一个 receivesBroadcastNotificationsOn 方法:

<?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-channelguzzlehttp/guzzle 软件包:

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

该软件包包含一个配置文件。但是,您不需要将此配置文件导出到您自己的应用程序中。您可以简单地使用VONAGE_KEYVONAGE_SECRET 环境变量来定义您的 Vonage 公共和私有密钥。

定义了密钥之后,您应该设置一个 VONAGE_SMS_FROM 环境变量,用于定义默认情况下应发送短信消息的电话号码。您可以在 Vonage 的控制面板中生成此电话号码:

VONAGE_SMS_FROM=15556666666

格式化短信通知

如果一个通知支持发送短信,您应该在通知类上定义一个 toVonage 方法。此方法将接收一个 $notifiable 实体,并应返回一个 Illuminate\Notifications\Messages\VonageMessage 实例:

use Illuminate\Notifications\Messages\VonageMessage;
 
/**
 * 获取通知的Vonage/SMS表示
 */
public function toVonage(object $notifiable): VonageMessage
{
    return (new VonageMessage)
                ->content('Your SMS message content');
}

Unicode内容

如果您的短信消息包含 Unicode 字符,您应该在构造 VonageMessage 实例时调用 unicode 方法:

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 方法:

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');
}

添加客户端引用

如果您想跟踪每个用户、团队或客户的成本,您可以在通知中添加一个“客户端引用”。Vonage 允许您使用这个客户端引用生成报告,以便更好地了解特定客户的短信使用情况。客户端引用可以是一个字符串,最多 40 个字符:

use Illuminate\Notifications\Messages\VonageMessage;
 
/**
 * 获取通知的Vonage/SMS表示
 */
public function toVonage(object $notifiable): VonageMessage
{
    return (new VonageMessage)
                ->clientReference((string) $notifiable->id)
                ->content('您的短信内容');
}

路由短信通知

要将 Vonage 通知路由到正确的电话号码,可以在可通知实体上定义一个 routeNotificationForVonage 方法:

<?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 通知通道:

composer require laravel/slack-notification-channel

此外,您还必须为 Slack 工作空间创建一个 Slack 应用

如果您只需要将通知发送到创建应用程序的相同 Slack 工作空间,您应该确保您的应用程序具有 chat:writechat:write.publicchat:write.customize 范围。这些范围可以从 Slack 的“OAuth和权限”应用程序管理选项卡中添加。

接下来,将应用程序的 services.php 配置文件中的 SlackBotUser OAuth 令牌复制到 slack 配置数组中。此令牌可以在 Slack 的“OAuth和权限”选项卡中找到:

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

应用程序分发

如果您的应用程序将通知发送到由应用程序用户拥有的外部 Slack 工作空间,您需要通过 Slack “分发”您的应用程序。通过 Slack 的“分发管理”选项卡可以管理应用程序的分发。分发应用程序后,您可以使用 Socialite 来代表应用程序用户获取 Slack 机器人令牌

格式化 Slack 通知

如果一个通知支持发送为 Slack 消息,您应该在通知类上定义一个 toSlack 方法。该方法将接收一个$notifiable实体,并应返回一个 Illuminate\Notifications\Slack\SlackMessage 实例。您可以使用 Slack 的块套件 API 构建丰富的通知。以下示例可以在 Slack 的块构建器中预览:

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.');
                $block->field("*Invoice No:*\n1000")->markdown();
                $block->field("*Invoice Recipient:*\ntaylor@laravel.com")->markdown();
            })
            ->dividerBlock()
            ->sectionBlock(function (SectionBlock $block) {
                $block->text('Congratulations!');
            });
}

Slack交互

Slack 的块套件通知系统提供了处理用户交互的强大功能。要使用这些功能,您的 Slack 应用程序应该启用“交互性”并配置一个指向由您的应用程序提供的 URL 的“请求URL”。这些设置可以在 Slack 的“交互性和快捷方式”应用程序管理选项卡中管理。

在下面的示例中,我们使用了 actionsBlock 方法,Slack 将使用一个包含有关哪个用户点击了按钮、点击的按钮的 ID 等的负载发送一个 POST 请求到您的“请求 URL”。您的应用程序可以根据负载来确定要采取的操作。您还应该验证请求是否由 Slack 发出:

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 实例:

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 块

如果您想快速检查构建的块,可以在 SlackMessage 实例上调用 dd 方法。dd 方法将会生成并转储一个指向Slack 的 Block Kit 构建器的 URL,在浏览器中显示消息的预览。您可以将 dd 方法的第二个参数设置为true,以转储原始负载:

return (new SlackMessage)
        ->text('您的一笔发票已付款!')
        ->headerBlock('发票已付款')
        ->dd();

路由 Slack 通知

要将 Slack 通知发送到正确的 Slack 团队和频道,您可以在可通知模型上定义一个 routeNotificationForSlack 方法。此方法可以返回以下三个值之一:

  • null - 这会将路由推迟到通知本身中配置的通道。您在构建 SlackMessage 时可以使用 to 方法来配置通知中的通道。
  • 指定要将通知发送到的 Slack 频道的字符串,例如 #support-channel
  • SlackRoute 实例,它允许您指定 OAuth 令牌和频道名称,例如 SlackRoute::make($this->slack_channel, $this->slack_token)。此方法应用于将通知发送到外部工作空间。

例如,从 routeNotificationForSlack 方法返回 #support-channel 将把通知发送到与 Bot User OAuth 令牌相关联的工作空间中的 #support-channel 频道:

<?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 应用程序必须经过分发

当然,您通常会希望将通知发送到应用程序用户拥有的 Slack 工作空间。为此,您首先需要为用户获取一个 Slack OAuth 令牌。幸运的是,Laravel Socialite 包含一个 Slack 驱动程序,可以方便地使用 Slack 对您的应用程序用户进行身份验证并获取 Bot 令牌

获得 Bot 令牌并将其存储在应用程序数据库中后,您可以使用 SlackRoute::make 方法将通知路由到用户的工作空间。此外,您的应用程序可能需要给用户提供一个机会来指定通知应发送到哪个频道:

<?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 方法来设置所需的语言。在评估通知时,应用程序会切换到此区域设置,然后在评估完成后恢复到以前的区域设置:

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

还可以通过 Notification 门面类对多个可通知条目进行本地化,以指定通知的区域设置:

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

用户首选区域设置

有时,应用程序会存储每个用户的首选区域设置。通过在可通知模型上实现 HasLocalePreference 约定,您可以指示 Laravel 在向模型发送通知和邮件时使用此存储的区域设置:

use Illuminate\Contracts\Translation\HasLocalePreference;

class User extends Model implements HasLocalePreference
{
    /**
     * 获取用户的首选区域设置。
     */
    public function preferredLocale(): string
    {
        return $this->locale;
    }
}

一旦实现了接口,Laravel 将自动在向模型发送通知和邮件时使用首选区域设置。因此,在使用此接口时不需要调用 locale 方法:

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

测试

您可以使用 Notification 门面类的 fake 方法来防止通知被发送。通常,发送通知与您实际测试的代码无关。很可能,简单地断言 Laravel 是否被指示发送了特定的通知就足够了。

调用 Notification 门面类的 fake 方法后,您可以断言是否被指示发送了通知,并甚至检查通知收到的数据:

<?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();
        
        // 执行订单送货...
 
        // 断言没有发送通知...
        Notification::assertNothingSent();

        // 断言发送了通知给给定的用户...
        Notification::assertSentTo(
            [$user], OrderShipped::class
        );

        // 断言未发送通知...
        Notification::assertNotSentTo(
            [$user], AnotherNotification::class
        );

        // 断言发送了指定数量的通知...
        Notification::assertCount(3);
    }
}

您可以将闭包传递给 assertSentToassertNotSentTo 方法,以断言是否发送了满足给定“真实测试”的通知。如果至少发送了一个满足给定真实测试的通知,则断言将成功:

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

按需通知

如果您测试的代码发送按需通知,可以使用 assertSentOnDemand 方法来测试是否发送了即时通知:

Notification::assertSentOnDemand(OrderShipped::class);

通过将闭包作为第二个参数传递给 assertSentOnDemand 方法,您可以确定是否发送了正确的“路由”地址的即时通知:

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

通知事件

通知发送事件

当通知被发送时,通知系统会触发 Illuminate\Notifications\Events\NotificationSending 事件。该事件包含了"notifiable"实体和通知实例本身。您可以在应用程序的 EventServiceProvider 中注册对此事件的监听器:

use App\Listeners\CheckNotificationStatus;
use Illuminate\Notifications\Events\NotificationSending;
 
/**
 * The event listener mappings for the application.
 *
 * @var array
 */
protected $listen = [
    NotificationSending::class => [
        CheckNotificationStatus::class,
    ],
];

如果 NotificationSending 事件的事件监听器在其 handle 方法中返回 false,则该通知将不会被发送:

use Illuminate\Notifications\Events\NotificationSending;
 
/**
 * Handle the event.
 */
public function handle(NotificationSending $event): void
{
    return false;
}

在事件监听器中,您可以通过访问事件的 notifiablenotificationchannel 属性来了解有关通知接收者或通知本身的更多信息:

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

通知已发送事件

当通知被发送时,通知系统会触发 Illuminate\Notifications\Events\NotificationSent 事件。该事件包含了"notifiable"实体和通知实例本身。您可以在 EventServiceProvider 中注册对此事件的监听器:

use App\Listeners\LogNotification;
use Illuminate\Notifications\Events\NotificationSent;
 
/**
 * The event listener mappings for the application.
 *
 * @var array
 */
protected $listen = [
    NotificationSent::class => [
        LogNotification::class,
    ],
];

EventServiceProvider 中注册完监听器后,使用 event:generate Artisan命令快速生成监听器类。

在事件监听器中,您可以通过访问事件的 notifiablenotificationchannelresponse 属性来了解有关通知接收者或通知本身的更多信息:

/**
 * 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
 
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
 
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
    {
        // ...
    }
}

Vote Vote Cancel Collect Collect Cancel

<< 上一篇: 邮件

>> 下一篇: 包开发