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

双重多态

  1. 通知对象多态:$notification->toMail() / toSms()
  2. 通道驱动器多态:$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 多态” 的典范。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐