在 Laravel 中,`Notification` 类如何通过 `via()` 方法和不同 `toMail()` / `toSms()` 方法实现多态发送?。
/ 调用微信 API]);组件作用多态体现via()声明目标通道返回通道名列表toMail()toSms()构建通道特定消息动态方法调用执行实际发送统一send()接口🔚Laravel 通知系统的多态本质“同一个通知对象,能以多种方式被消费”而框架通过命名约定 + 动态调用在保持 API 简洁的同时,实现了高度可扩展的分发机制——这正是所欣赏的“在动态语言中通过约定实现 OOP 多态”的典范。
Laravel 的通知系统(Notifications)是 多态分发(Polymorphic Dispatch) 的经典实现:它通过 via() 方法声明目标通道,再通过命名约定的方法(如 toMail()、toSms())动态调用对应通道的构建逻辑,无需显式 if-else 判断。整个过程由 NotificationSender 和通道驱动器(Channel Driver)协同完成,完美体现了 “约定优于配置” + “多态” 的设计哲学。
一、核心机制:via() + 命名方法 = 多态分发
1. via() 方法:声明要使用的通道
class InvoicePaid extends Notification
{
public function via($notifiable): array
{
return ['mail', 'sms', 'slack']; // ← 返回通道名数组
}
}
- 返回值是通道驱动器的名称(对应
config/queue.php中的channels或内置通道) - Laravel 内置通道:
mail,database,broadcast,nexmo(短信),slack等
2. 命名方法:为每个通道提供消息构建器
class InvoicePaid extends Notification
{
// 为 'mail' 通道构建消息
public function toMail($notifiable): MailMessage
{
return (new MailMessage)
->subject('Invoice Paid')
->line('Your invoice has been paid.');
}
// 为 'sms' 通道构建消息
public function toSms($notifiable): string
{
return "Hi {$notifiable->name}, your invoice is paid!";
}
// 为 'slack' 通道构建消息
public function toSlack($notifiable): SlackMessage
{
return (new SlackMessage)->content('Invoice paid!');
}
}
✅ 命名约定:
to{ChannelName}()(首字母大写)
二、运行时如何实现多态调用?
当执行 $user->notify(new InvoicePaid()) 时,Laravel 内部流程如下:
步骤 1:获取通知通道列表
$channels = $notification->via($notifiable); // ['mail', 'sms', 'slack']
步骤 2:对每个通道,动态调用 to{Channel} 方法
// Illuminate\Notifications\ChannelManager
foreach ($channels as $channel) {
$class = "to" . ucfirst($channel); // 'mail' → 'toMail'
if (method_exists($notification, $class)) {
$message = $notification->{$class}($notifiable); // ← 多态调用!
$this->driver($channel)->send($notifiable, $message);
}
}
关键源码(简化):
// Illuminate\Notifications\NotificationSender
public function sendNow($notifiables, $notification)
{
foreach ($this->channels($notification) as $channel) {
$method = 'to' . ucfirst($channel);
if (method_exists($notification, $method)) {
$message = $notification->$method($notifiable);
$this->manager->driver($channel)->send($notifiable, $ message);
}
}
}
🔁 这就是多态:
同一个$notification对象,
根据$channel值动态调用不同方法,
无任何 if-else 判断通道类型!
三、通道驱动器(Channel Driver)的多态
每个通道(如 mail, sms)都有对应的 驱动器类,实现统一的 send() 接口:
| 通道 | 驱动器类 | send() 实现 |
|---|---|---|
mail |
MailChannel |
调用 Mail 门面发送 Mailable |
sms |
NexmoSmsChannel |
调用 Nexmo API |
slack |
SlackWebhookChannel |
发送 HTTP 请求到 Slack webhook |
// 统一接口
interface NotificationChannel
{
public function send($notifiable, $notification);
}
// MailChannel 实现
class MailChannel implements NotificationChannel
{
public function send($notifiable, $notification)
{
Mail::to($notifiable->email)->send($notification);
}
}
✅ 双重多态:
- 通知对象多态:
$notification->toMail()/toSms()- 通道驱动器多态:
$driver->send()
四、自定义通道:扩展多态体系
1. 创建自定义通道驱动器
// app/Notifications/Channels/WechatChannel.php
class WechatChannel
{
public function send($notifiable, $message)
{
// 调用微信 API
Http::post('https://api.wechat.com/send', [
'user' => $notifiable->wechat_id,
'text' => $message,
]);
}
}
2. 在通知类中添加 toWechat() 方法
class InvoicePaid extends Notification
{
public function via($notifiable): array
{
return ['mail', 'wechat']; // ← 包含自定义通道
}
public function toWechat($notifiable): string
{
return "【发票】{$notifiable->name},您的发票已支付!";
}
}
3. (可选)在 AppServiceProvider 中注册通道
Notification::extend('wechat', function ($app) {
return new WechatChannel;
});
✅ 扩展无需修改核心代码:符合开闭原则!
五、为什么这是“优雅的多态”?
| 优势 | 说明 |
|---|---|
| 无条件分支 | 通过 method_exists + 动态调用,避免 if-else |
| 开闭原则 | 新增通道 = 新方法 + 新驱动器,不改通知基类 |
| 单一职责 | 每个 toXxx() 方法只关注一种通道的消息构建 |
| 约定优于配置 | 方法命名自动关联通道,无需额外配置 |
| 可测试性 | 可单独测试 toMail() 返回的 MailMessage |
六、对比:如果不用多态会怎样?
// ❌ 反例:硬编码 if-else
public function send($notifiable, $channels)
{
foreach ($channels as $channel) {
if ($channel === 'mail') {
$this->sendEmail($notifiable);
} elseif ($channel === 'sms') {
$this->sendSms($notifiable);
} // ... 每新增通道都要改这里
}
}
💡 Laravel 的通知系统通过 “方法名即协议”,将类型分发转化为方法存在性检查,在动态语言中实现了优雅的多态。
七、总结
| 组件 | 作用 | 多态体现 |
|---|---|---|
via() |
声明目标通道 | 返回通道名列表 |
toMail()/toSms() |
构建通道特定消息 | 动态方法调用 |
| Channel Driver | 执行实际发送 | 统一 send() 接口 |
🔚 Laravel 通知系统的多态本质:
“同一个通知对象,能以多种方式被消费”,
而框架通过 命名约定 + 动态调用,
在保持 API 简洁的同时,实现了高度可扩展的分发机制——
这正是所欣赏的 “在动态语言中通过约定实现 OOP 多态” 的典范。
更多推荐



所有评论(0)