消息通知
这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。
通知系统
简介
除了支持邮件发送, 功能外,Laravel 还提供了跨多种渠道发送通知的能力,包括邮件、短信(通过 Vonage,原Nexmo服务)以及Slack消息。此外,社区还开发了 community built notification channels 大量通知渠道扩展,支持通过数十种不同渠道发送通知!这些通知还可以存储在数据库中,以便在您的Web界面中展示。
通常情况下,通知应该是简短的、信息性的消息,用于通知用户在你的应用程序中发生了某些事情。例如,如果你正在编写一个计费应用程序,你可能会通过电子邮件和短信渠道向用户发送一个“发票已支付”通知。
生成通知
在 Laravel 中,每个通知由一个单独的类表示,通常存储在 app/Notifications
目录中。如果你在应用程序中没有看到该目录,不用担心——当你运行 make:notification
Artisan 命令时,它会自动为你创建:
php artisan make:notification InvoicePaid
该命令会在 app/Notifications
目录中放置一个新的通知类。每个通知类都包含一个 via
方法,以及若干消息构建方法(如 toMail
或 toDatabase
),用于将通知转换为针对特定渠道的消息。
发送通知
使用 Notifiable 特征(Trait)
通知可以通过两种方式发送:使用 Notifiable
特征的 notify
方法,或者使用 Notification
门面(facade)。Notifiable
特征默认包含在你应用的 App\Models\User
模型中:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
}
由该特征提供的 notify
方法需要接收一个通知实例:
use App\Notifications\InvoicePaid;
$user->notify(new InvoicePaid($invoice));
[!注意]
请记住,你可以在任何模型上使用Notifiable
特征,并不仅限于User
模型。
使用 Notification 门面(Facade)
或者,你也可以通过 Notification
门面 发送通知。当你需要向多个可通知实体(例如用户集合)发送通知时,这种方法非常有用。要使用门面发送通知,将所有可通知实体和通知实例传递给 send
方法:
use Illuminate\Support\Facades\Notification;
Notification::send($users, new InvoicePaid($invoice));
你还可以使用 sendNow
方法立即发送通知。即使通知实现了 ShouldQueue
接口,该方法也会立即发送通知:
Notification::sendNow($developers, new DeploymentCompleted($deployment));
指定发送渠道
每个通知类都有一个 via
方法,用于确定通知将通过哪些渠道发送。通知可以通过 mail
、database
、broadcast
、vonage
和 slack
渠道发送。
[!注意]
如果你想使用其他发送渠道,例如 Telegram 或 Pusher,可以查看社区维护的 Laravel Notification Channels 网站。
via
方法接收一个 $notifiable
实例,它将是通知发送目标的类的实例。你可以使用 $notifiable
来确定通知应该通过哪些渠道发送:
/**
* 获取通知的发送渠道。
*
* @return array<int, string>
*/
public function via(object $notifiable): array
{
return $notifiable->prefers_sms ? ['vonage'] : ['mail', 'database'];
}
队列通知
[!警告]
在将通知加入队列之前,你应该先配置队列并 启动队列工作进程。
发送通知可能会耗费时间,尤其是当某个渠道需要调用外部 API 来发送通知时。为了加快应用程序的响应时间,可以通过在通知类中添加 ShouldQueue
接口和 Queueable
特征,让通知加入队列。使用 make:notification
命令生成的所有通知类中,这个接口和特征已经被导入,因此你可以立即将它们添加到你的通知类中:
<?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
方法:
$delay = now()->addMinutes(10);
$user->notify((new InvoicePaid($invoice))->delay($delay));
你也可以向 delay
方法传递一个数组,为特定渠道指定延迟时间:
$user->notify((new InvoicePaid($invoice))->delay([
'mail' => now()->addMinutes(5),
'sms' => now()->addMinutes(10),
]));
或者,你可以在通知类本身定义一个 withDelay
方法。withDelay
方法应该返回一个渠道名称和延迟时间的数组:
/**
* 确定通知的发送延迟时间。
*
* @return array<string, \Illuminate\Support\Carbon>
*/
public function withDelay(object $notifiable): array
{
return [
'mail' => now()->addMinutes(5),
'sms' => now()->addMinutes(10),
];
}
自定义通知队列连接
默认情况下,加入队列的通知会使用应用程序的默认队列连接。如果你想为特定通知指定不同的连接,可以在通知构造函数中调用 onConnection
方法:
<?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;
/**
* 创建一个新的通知实例。
*/
public function __construct()
{
$this->onConnection('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',
];
}
队列通知中间件
加入队列的通知可以定义中间件,就像队列任务一样。要开始使用,可以在通知类中定义一个 middleware
方法。该方法会接收 $notifiable
和 $channel
变量,允许你根据通知的目标自定义返回的中间件:
use Illuminate\Queue\Middleware\RateLimited;
/**
* 获取通知队列任务应通过的中间件。
*
* @return array<int, object>
*/
public function middleware(object $notifiable, string $channel)
{
return match ($channel) {
'mail' => [new RateLimited('postmark')],
'slack' => [new RateLimited('slack')],
default => [],
};
}
队列通知与数据库事务
当在数据库事务中派发队列通知时,这些通知可能在数据库事务提交之前就被队列处理。这种情况下,你在数据库事务中对模型或数据库记录所做的任何更新可能尚未反映在数据库中。此外,在事务中创建的任何模型或数据库记录可能尚不存在于数据库中。如果你的通知依赖这些模型,当处理发送队列通知的任务时,可能会发生意外错误。
如果你的队列连接的 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;
/**
* 创建一个新的通知实例。
*/
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', '#slack-channel')
->route('broadcast', [new Channel('channel-name')])
->notify(new InvoicePaid($invoice));
如果你希望在向 mail
路由发送按需通知时提供收件人的姓名,可以传递一个数组,其中数组的键是电子邮件地址,值是姓名:
Notification::route('mail', [
'barrett@example.com' => 'Barrett Blair',
])->notify(new InvoicePaid($invoice));
使用 routes
方法,你可以一次性为多个通知渠道提供临时路由信息:
Notification::routes([
'mail' => ['barrett@example.com' => 'Barrett Blair'],
'vonage' => '5555555555',
])->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 邮件模板,并附带纯文本版本。下面是 mail
渠道生成的邮件示例:
[!注意]
发送邮件通知时,请确保在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(
'mail.invoice.paid', ['invoice' => $this->invoice]
);
}
你可以通过将纯文本视图名称作为数组的第二个元素传递给 view
方法,为邮件消息指定纯文本视图:
/**
* 获取通知的邮件表示。
*/
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)->view(
['mail.invoice.paid', 'mail.invoice.paid-text'], // HTML 视图 + 纯文本视图
['invoice' => $this->invoice]
);
}
或者,如果你的消息只有纯文本视图,可以使用 text
方法:
/**
* 获取通知的邮件表示。
*/
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)->text(
'mail.invoice.paid-text', ['invoice' => $this->invoice] // 仅纯文本邮件
);
}
自定义发件人
默认情况下,邮件的发件人 / from 地址是在 config/mail.php
配置文件中定义的。
但是,你可以通过 from
方法为某个特定通知指定发件人地址:
/**
* 获取通知的邮件表示。
*/
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->from('barrett@example.com', 'Barrett Blair')
->line('...');
}
自定义收件人
当通过 mail
渠道发送通知时,通知系统会自动查找可通知实体上的 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;
/**
* 为 mail 渠道路由通知。
*
* @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('...');
}
自定义 Mailer
默认情况下,邮件通知会使用 config/mail.php
配置文件中定义的默认 mailer 发送。
但是,你可以在运行时通过在构建消息时调用 mailer
方法来指定一个不同的 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
方法的第一个参数接受文件的绝对路径:
/**
* 获取通知的邮件表示。
*/
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->greeting('Hello!')
->attach('/path/to/file');
}
[!注意]
通知邮件消息提供的attach
方法同样支持 attachable 对象。
请查阅完整的 attachable 对象文档 了解更多信息。
当向消息附加文件时,你还可以通过将一个 array
作为第二个参数传递给 attach
方法,来指定显示名称和/或 MIME 类型:
/**
* 获取通知的邮件表示。
*/
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->greeting('Hello!')
->attach('/path/to/file', [
'as' => 'name.pdf',
'mime' => 'application/pdf',
]);
}
与在 mailable 对象中附加文件不同,你不能直接使用 attachFromStorage
从存储磁盘附加文件。
你应该改为使用 attach
方法,并提供存储磁盘上文件的绝对路径。
或者,你也可以在 toMail
方法中返回一个 mailable:
use App\Mail\InvoicePaid as InvoicePaidMailable;
/**
* 获取通知的邮件表示。
*/
public function toMail(object $notifiable): Mailable
{
return (new InvoicePaidMailable($this->invoice))
->to($notifiable->email)
->attachFromStorage('/path/to/file');
}
在需要时,可以使用 attachMany
方法向消息附加多个文件:
/**
* 获取通知的邮件表示。
*/
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
方法时,你应该提供一个文件名,用来分配给该附件:
/**
* 获取通知的邮件表示。
*/
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
方法将标签和元数据添加到电子邮件消息中:
/**
* 获取通知的邮件表示。
*/
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 的文档,以获取更多关于其对 标签 和 元数据 的支持信息。
如果你的应用使用 Amazon SES 发送电子邮件,你应该使用 metadata
方法,将 SES “标签” 附加到消息中。
自定义 Symfony Message
MailMessage
类的 withSymfonyMessage
方法允许你注册一个闭包(closure),在发送消息之前,该闭包会接收 Symfony Message 实例。
这为你提供了一个在消息被投递前进行深度自定义的机会:
use Symfony\Component\Mime\Email;
/**
* 获取通知的邮件表示。
*/
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;
/**
* 获取通知的邮件表示。
*/
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;
/**
* 获取通知的邮件表示。
*/
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
方法。
不过,与其使用 line
和 action
方法来构建通知,不如使用 markdown
方法来指定应使用的 Markdown 模板名称。
你希望提供给模板使用的数据数组可以作为该方法的第二个参数传递:
/**
* 获取通知的邮件表示。
*/
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>
[!注意]
在编写 Markdown 邮件时,不要使用过多缩进。根据 Markdown 标准,Markdown 解析器会将缩进内容渲染为代码块。
按钮组件
按钮组件会渲染一个居中的按钮链接。
该组件接受两个参数:一个 url
,以及一个可选的 color
。
支持的颜色有:primary
、green
和 red
。
你可以在通知中添加任意数量的按钮组件:
<x-mail::button :url="$url" color="green">
View Invoice
</x-mail::button>
Panel Component
The panel component renders the given block of text in a panel that has a slightly different background color than the rest of the notification. This allows you to draw attention to a given block of text:
<x-mail::panel>
This is the panel content.
</x-mail::panel>
Table Component
The table component allows you to transform a Markdown table into an HTML table. The component accepts the Markdown table as its content. Table column alignment is supported using the default Markdown table alignment syntax:
<x-mail::table>
| Laravel | Table | Example |
| ------------- | :-----------: | ------------: |
| Col 2 is | Centered | $10 |
| Col 3 is | Right-Aligned | $20 |
</x-mail::table>
Customizing the Components
You may export all of the Markdown notification components to your own application for customization. To export the components, use the vendor:publish
Artisan command to publish the laravel-mail
asset tag:
php artisan vendor:publish --tag=laravel-mail
This command will publish the Markdown mail components to the resources/views/vendor/mail
directory. The mail
directory will contain an html
and a text
directory, each containing their respective representations of every available component. You are free to customize these components however you like.
Customizing the CSS
After exporting the components, the resources/views/vendor/mail/html/themes
directory will contain a default.css
file. You may customize the CSS in this file and your styles will automatically be in-lined within the HTML representations of your Markdown notifications.
If you would like to build an entirely new theme for Laravel's Markdown components, you may place a CSS file within the html/themes
directory. After naming and saving your CSS file, update the theme
option of the mail
configuration file to match the name of your new theme.
To customize the theme for an individual notification, you may call the theme
method while building the notification's mail message. The theme
method accepts the name of the theme that should be used when sending the notification:
/**
* 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 Notifications
Prerequisites
The database
notification channel stores the notification information in a database table. This table will contain information such as the notification type as well as a JSON data structure that describes the notification.
You can query the table to display the notifications in your application's user interface. But, before you can do that, you will need to create a database table to hold your notifications. You may use the make:notifications-table
command to generate a migration with the proper table schema:
php artisan make:notifications-table
php artisan migrate
[!NOTE]
If your notifiable models are using UUID or ULID primary keys, you should replace themorphs
method with uuidMorphs or ulidMorphs in the notification table migration.
Formatting Database Notifications
If a notification supports being stored in a database table, you should define a toDatabase
or toArray
method on the notification class. This method will receive a $notifiable
entity and should return a plain PHP array. The returned array will be encoded as JSON and stored in the data
column of your notifications
table. Let's take a look at an example toArray
method:
/**
* 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,
];
}
When a notification is stored in your application's database, the type
column will be set to the notification's class name by default, and the read_at
column will be null
. However, you can customize this behavior by defining the databaseType
and initialDatabaseReadAtValue
methods in your notification class:
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
The toArray
method is also used by the broadcast
channel to determine which data to broadcast to your JavaScript powered frontend. If you would like to have two different array representations for the database
and broadcast
channels, you should define a toDatabase
method instead of a toArray
method.
Accessing the Notifications
Once notifications are stored in the database, you need a convenient way to access them from your notifiable entities. The Illuminate\Notifications\Notifiable
trait, which is included on Laravel's default App\Models\User
model, includes a notifications
Eloquent relationship that returns the notifications for the entity. To fetch notifications, you may access this method like any other Eloquent relationship. By default, notifications will be sorted by the created_at
timestamp with the most recent notifications at the beginning of the collection:
$user = App\Models\User::find(1);
foreach ($user->notifications as $notification) {
echo $notification->type;
}
If you want to retrieve only the "unread" notifications, you may use the unreadNotifications
relationship. Again, these notifications will be sorted by the created_at
timestamp with the most recent notifications at the beginning of the collection:
$user = App\Models\User::find(1);
foreach ($user->unreadNotifications as $notification) {
echo $notification->type;
}
[!NOTE]
To access your notifications from your JavaScript client, you should define a notification controller for your application which returns the notifications for a notifiable entity, such as the current user. You may then make an HTTP request to that controller's URL from your JavaScript client.
Marking Notifications as Read
Typically, you will want to mark a notification as "read" when a user views it. The Illuminate\Notifications\Notifiable
trait provides a markAsRead
method, which updates the read_at
column on the notification's database record:
$user = App\Models\User::find(1);
foreach ($user->unreadNotifications as $notification) {
$notification->markAsRead();
}
However, instead of looping through each notification, you may use the markAsRead
method directly on a collection of notifications:
$user->unreadNotifications->markAsRead();
You may also use a mass-update query to mark all of the notifications as read without retrieving them from the database:
$user = App\Models\User::find(1);
$user->unreadNotifications()->update(['read_at' => now()]);
You may delete
the notifications to remove them from the table entirely:
$user->notifications()->delete();
Broadcast Notifications
Prerequisites
Before broadcasting notifications, you should configure and be familiar with Laravel's event broadcasting services. Event broadcasting provides a way to react to server-side Laravel events from your JavaScript powered frontend.
Formatting Broadcast Notifications
The broadcast
channel broadcasts notifications using Laravel's event broadcasting services, allowing your JavaScript powered frontend to catch notifications in realtime. If a notification supports broadcasting, you can define a toBroadcast
method on the notification class. This method will receive a $notifiable
entity and should return a BroadcastMessage
instance. If the toBroadcast
method does not exist, the toArray
method will be used to gather the data that should be broadcast. The returned data will be encoded as JSON and broadcast to your JavaScript powered frontend. Let's take a look at an example toBroadcast
method:
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,
]);
}
Broadcast Queue Configuration
All broadcast notifications are queued for broadcasting. If you would like to configure the queue connection or queue name that is used to queue the broadcast operation, you may use the onConnection
and onQueue
methods of the BroadcastMessage
:
return (new BroadcastMessage($data))
->onConnection('sqs')
->onQueue('broadcasts');
Customizing the Notification Type
In addition to the data you specify, all broadcast notifications also have a type
field containing the full class name of the notification. If you would like to customize the notification type
, you may define a broadcastType
method on the notification class:
/**
* Get the type of the notification being broadcast.
*/
public function broadcastType(): string
{
return 'broadcast.message';
}
Listening for Notifications
Notifications will broadcast on a private channel formatted using a {notifiable}.{id}
convention. So, if you are sending a notification to an App\Models\User
instance with an ID of 1
, the notification will be broadcast on the App.Models.User.1
private channel. When using Laravel Echo, you may easily listen for notifications on a channel using the notification
method:
Echo.private('App.Models.User.' + userId)
.notification((notification) => {
console.log(notification.type);
});
Customizing the Notification Channel
If you would like to customize which channel that an entity's broadcast notifications are broadcast on, you may define a receivesBroadcastNotificationsOn
method on the notifiable entity:
<?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;
}
}
SMS Notifications
Prerequisites
Sending SMS notifications in Laravel is powered by Vonage (formerly known as Nexmo). Before you can send notifications via Vonage, you need to install the laravel/vonage-notification-channel
and guzzlehttp/guzzle
packages:
composer require laravel/vonage-notification-channel guzzlehttp/guzzle
The package includes a configuration file. However, you are not required to export this configuration file to your own application. You can simply use the VONAGE_KEY
and VONAGE_SECRET
environment variables to define your Vonage public and secret keys.
After defining your keys, you should set a VONAGE_SMS_FROM
environment variable that defines the phone number that your SMS messages should be sent from by default. You may generate this phone number within the Vonage control panel:
VONAGE_SMS_FROM=15556666666
Formatting SMS Notifications
If a notification supports being sent as an SMS, you should define a toVonage
method on the notification class. This method will receive a $notifiable
entity and should return an Illuminate\Notifications\Messages\VonageMessage
instance:
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 Content
If your SMS message will contain unicode characters, you should call the unicode
method when constructing the VonageMessage
instance:
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();
}
Customizing the "From" Number
If you would like to send some notifications from a phone number that is different from the phone number specified by your VONAGE_SMS_FROM
environment variable, you may call the from
method on a VonageMessage
instance:
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');
}
Adding a Client Reference
If you would like to keep track of costs per user, team, or client, you may add a "client reference" to the notification. Vonage will allow you to generate reports using this client reference so that you can better understand a particular customer's SMS usage. The client reference can be any string up to 40 characters:
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');
}
Routing SMS Notifications
To route Vonage notifications to the proper phone number, define a routeNotificationForVonage
method on your notifiable entity:
<?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 Notifications
Prerequisites
Before sending Slack notifications, you should install the Slack notification channel via Composer:
composer require laravel/slack-notification-channel
Additionally, you must create a Slack App for your Slack workspace.
If you only need to send notifications to the same Slack workspace that the App is created in, you should ensure that your App has the chat:write
, chat:write.public
, and chat:write.customize
scopes. These scopes can be added from the "OAuth & Permissions" App management tab within Slack.
Next, copy the App's "Bot User OAuth Token" and place it within a slack
configuration array in your application's services.php
configuration file. This token can be found on the "OAuth & Permissions" tab within Slack:
'slack' => [
'notifications' => [
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
],
],
App Distribution
If your application will be sending notifications to external Slack workspaces that are owned by your application's users, you will need to "distribute" your App via Slack. App distribution can be managed from your App's "Manage Distribution" tab within Slack. Once your App has been distributed, you may use Socialite to obtain Slack Bot tokens on behalf of your application's users.
Formatting Slack Notifications
If a notification supports being sent as a Slack message, you should define a toSlack
method on the notification class. This method will receive a $notifiable
entity and should return an Illuminate\Notifications\Slack\SlackMessage
instance. You can construct rich notifications using Slack's Block Kit API. The following example may be previewed in Slack's Block Kit builder:
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!');
});
}
Using Slack's Block Kit Builder Template
Instead of using the fluent message builder methods to construct your Block Kit message, you may provide the raw JSON payload generated by Slack's Block Kit Builder to the usingBlockKitTemplate
method:
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 Interactivity
Slack's Block Kit notification system provides powerful features to handle user interaction. To utilize these features, your Slack App should have "Interactivity" enabled and a "Request URL" configured that points to a URL served by your application. These settings can be managed from the "Interactivity & Shortcuts" App management tab within Slack.
In the following example, which utilizes the actionsBlock
method, Slack will send a POST
request to your "Request URL" with a payload containing the Slack user who clicked the button, the ID of the clicked button, and more. Your application can then determine the action to take based on the payload. You should also verify the request was made by 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');
});
}
Confirmation Modals
If you would like users to be required to confirm an action before it is performed, you may invoke the confirm
method when defining your button. The confirm
method accepts a message and a closure which receives a ConfirmObject
instance:
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');
}
);
});
}
Inspecting Slack Blocks
If you would like to quickly inspect the blocks you've been building, you can invoke the dd
method on the SlackMessage
instance. The dd
method will generate and dump a URL to Slack's Block Kit Builder, which displays a preview of the payload and notification in your browser. You may pass true
to the dd
method to dump the raw payload:
return (new SlackMessage)
->text('One of your invoices has been paid!')
->headerBlock('Invoice Paid')
->dd();
Routing Slack Notifications
To direct Slack notifications to the appropriate Slack team and channel, define a routeNotificationForSlack
method on your notifiable model. This method can return one of three values:
null
- which defers routing to the channel configured in the notification itself. You may use theto
method when building yourSlackMessage
to configure the channel within the notification.- A string specifying the Slack channel to send the notification to, e.g.
#support-channel
. - A
SlackRoute
instance, which allows you to specify an OAuth token and channel name, e.g.SlackRoute::make($this->slack_channel, $this->slack_token)
. This method should be used to send notifications to external workspaces.
For instance, returning #support-channel
from the routeNotificationForSlack
method will send the notification to the #support-channel
channel in the workspace associated with the Bot User OAuth token located in your application's services.php
configuration file:
<?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';
}
}
Notifying External Slack Workspaces
[!NOTE]
Before sending notifications to external Slack workspaces, your Slack App must be distributed.
Of course, you will often want to send notifications to the Slack workspaces owned by your application's users. To do so, you will first need to obtain a Slack OAuth token for the user. Thankfully, Laravel Socialite includes a Slack driver that will allow you to easily authenticate your application's users with Slack and obtain a bot token.
Once you have obtained the bot token and stored it within your application's database, you may utilize the SlackRoute::make
method to route a notification to the user's workspace. In addition, your application will likely need to offer an opportunity for the user to specify which channel notifications should be sent to:
<?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);
}
}
Localizing Notifications
Laravel allows you to send notifications in a locale other than the HTTP request's current locale, and will even remember this locale if the notification is queued.
To accomplish this, the Illuminate\Notifications\Notification
class offers a locale
method to set the desired language. The application will change into this locale when the notification is being evaluated and then revert back to the previous locale when evaluation is complete:
$user->notify((new InvoicePaid($invoice))->locale('es'));
Localization of multiple notifiable entries may also be achieved via the Notification
facade:
Notification::locale('es')->send(
$users, new InvoicePaid($invoice)
);
User Preferred Locales
Sometimes, applications store each user's preferred locale. By implementing the HasLocalePreference
contract on your notifiable model, you may instruct Laravel to use this stored locale when sending a notification:
use Illuminate\Contracts\Translation\HasLocalePreference;
class User extends Model implements HasLocalePreference
{
/**
* Get the user's preferred locale.
*/
public function preferredLocale(): string
{
return $this->locale;
}
}
Once you have implemented the interface, Laravel will automatically use the preferred locale when sending notifications and mailables to the model. Therefore, there is no need to call the locale
method when using this interface:
$user->notify(new InvoicePaid($invoice));
Testing
You may use the Notification
facade's fake
method to prevent notifications from being sent. Typically, sending notifications is unrelated to the code you are actually testing. Most likely, it is sufficient to simply assert that Laravel was instructed to send a given notification.
After calling the Notification
facade's fake
method, you may then assert that notifications were instructed to be sent to users and even inspect the data the notifications received:
<?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 that a given number of notifications were sent...
Notification::assertCount(3);
});
<?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 that a given number of notifications were sent...
Notification::assertCount(3);
}
}
You may pass a closure to the assertSentTo
or assertNotSentTo
methods in order to assert that a notification was sent that passes a given "truth test". If at least one notification was sent that passes the given truth test then the assertion will be successful:
Notification::assertSentTo(
$user,
function (OrderShipped $notification, array $channels) use ($order) {
return $notification->order->id === $order->id;
}
);
On-Demand Notifications
If the code you are testing sends on-demand notifications, you can test that the on-demand notification was sent via the assertSentOnDemand
method:
Notification::assertSentOnDemand(OrderShipped::class);
By passing a closure as the second argument to the assertSentOnDemand
method, you may determine if an on-demand notification was sent to the correct "route" address:
Notification::assertSentOnDemand(
OrderShipped::class,
function (OrderShipped $notification, array $channels, object $notifiable) use ($user) {
return $notifiable->routes['mail'] === $user->email;
}
);
Notification Events
Notification Sending Event
When a notification is sending, the Illuminate\Notifications\Events\NotificationSending
event is dispatched by the notification system. This contains the "notifiable" entity and the notification instance itself. You may create event listeners for this event within your application:
use Illuminate\Notifications\Events\NotificationSending;
class CheckNotificationStatus
{
/**
* Handle the event.
*/
public function handle(NotificationSending $event): void
{
// ...
}
}
The notification will not be sent if an event listener for the NotificationSending
event returns false
from its handle
method:
/**
* Handle the event.
*/
public function handle(NotificationSending $event): bool
{
return false;
}
Within an event listener, you may access the notifiable
, notification
, and channel
properties on the event to learn more about the notification recipient or the notification itself:
/**
* Handle the event.
*/
public function handle(NotificationSending $event): void
{
// $event->channel
// $event->notifiable
// $event->notification
}
Notification Sent Event
When a notification is sent, the Illuminate\Notifications\Events\NotificationSent
event is dispatched by the notification system. This contains the "notifiable" entity and the notification instance itself. You may create event listeners for this event within your application:
use Illuminate\Notifications\Events\NotificationSent;
class LogNotification
{
/**
* Handle the event.
*/
public function handle(NotificationSent $event): void
{
// ...
}
}
Within an event listener, you may access the notifiable
, notification
, channel
, and response
properties on the event to learn more about the notification recipient or the notification itself:
/**
* Handle the event.
*/
public function handle(NotificationSent $event): void
{
// $event->channel
// $event->notifiable
// $event->notification
// $event->response
}
Custom Channels
Laravel ships with a handful of notification channels, but you may want to write your own drivers to deliver notifications via other channels. Laravel makes it simple. To get started, define a class that contains a send
method. The method should receive two arguments: a $notifiable
and a $notification
.
Within the send
method, you may call methods on the notification to retrieve a message object understood by your channel and then send the notification to the $notifiable
instance however you wish:
<?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...
}
}
Once your notification channel class has been defined, you may return the class name from the via
method of any of your notifications. In this example, the toVoice
method of your notification can return whatever object you choose to represent voice messages. For example, you might define your own VoiceMessage
class to represent these messages:
<?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
{
// ...
}
}
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: