Symfony Notifier Prelude 桥接组件实战:从 DSN 配置到模板短信发送的完整指南

原创2026-10-02 18:44:141,689 阅读
文章标签:后端Web框架

Symfony Notifier Prelude 桥接组件实战:从 DSN 配置到模板短信发送的完整指南

导读

本文围绕 Symfony Notifier 组件中的 Prelude 桥接(Bridge) 展开,系统讲解如何基于 Prelude Notify API 在 Symfony 应用中发送短信与 WhatsApp 消息。文章完整覆盖桥接的安装、DSN 配置、PreludeOptions 全参数用法,并深入源码与测试用例,解释 template_id 必填校验、ssl DSN 选项等底层实现。读完本文,你将能够独立完成 Prelude 桥接的接入、配置与消息发送,并理解其传输层的工作机制。

一、Prelude 桥接是什么

Prelude 桥接是 Symfony Notifier 的一个官方传输通道(transport),让应用可以通过统一的 NotifierInterface 抽象,将消息发送到 Prelude 平台的 Notify API。该桥接在 8.1 版本中被引入(见 CHANGELOG 中 "8.1 — Add bridge" 条目),并在 8.2 版本中新增了 ssl DSN 选项支持。

从 composer.json 可以看到该桥接的依赖约束:要求 php >= 8.4.1、symfony/http-client ^7.4|^8.0 以及 symfony/notifier ^8.2,属于 symfony-notifier-bridge 类型包,命名空间为 Symfony\Component\Notifier\Bridge\Prelude。

桥接目录中共包含三个核心类文件与对应的测试:

文件 职责
PreludeTransport.php 传输层:构造请求体、调用 /v2/notify 接口
PreludeTransportFactory.php 工厂:解析 DSN 并创建传输实例
PreludeOptions.php 消息选项:封装模板 ID 与各类可选参数

二、安装与启用

在项目根目录执行 Composer 安装命令:

composer require symfony/prelude-notifier

安装完成后,若你的项目未使用 Symfony Flex(即需要手动注册 Bundle),请参考 FrameworkBundle 的 Bundle 注册方式 在 config/bundles.php 或内核的 registerBundles() 中注册 Notifier 相关 Bundle。Symfony Flex 会自动完成注册与 config/packages/notifier.yaml 的生成。

三、DSN 配置:环境变量与 ssl 选项

在 .env 文件中配置传输 DSN,格式如下(完整示例见 Prelude README):

# API Key 必填
# Sender ID 可选(可在选项中或 DSN 中设置)
PRELUDE_DSN=prelude://YOUR_API_KEY@default?sender=YOUR_SENDER_ID

DSN 各部分含义:

  • prelude://:固定 scheme,由 PreludeTransportFactory::getSupportedSchemes() 声明为 ['prelude'];
  • YOUR_API_KEY:位于 @ 之前的用户信息(userinfo),在 PreludeTransportFactory::create() 中通过 getUser($dsn) 提取,缺失时会抛出 IncompleteDsnException;
  • default:主机占位符。工厂中 'default' === $dsn->getHost() ? null : $dsn->getHost() 表示:当主机为 default 时使用 PreludeTransport 内置的默认主机 api.prelude.dev;
  • sender=YOUR_SENDER_ID:可选查询参数,作为默认 Sender ID 写入传输实例;
  • ssl:8.2 新增的 DSN 选项,用于切换 HTTP/HTTPS(详见下文)。

ssl 选项与明文 HTTP 请求

CHANGELOG 中 8.2 版本的条目为:

Add the ssl DSN option to send requests over plain HTTP

即 8.2 起可通过 ssl 选项让请求走明文 HTTP。其底层实现链路如下:

  1. 工厂在 create() 末尾调用 ->setSsl($this->getSsl($dsn));
  2. AbstractTransportFactory::getSsl() 读取 dsn->getOption('ssl'),返回 null(未设置)、true 或 false(见 AbstractTransportFactory.php);
  3. AbstractTransport::setSsl() 存储该值,getHttpScheme() 依据 $this->ssl ?? static::SSL 返回 'https' 或 'http'(见 AbstractTransport.php)。

因此实际可用的 DSN 形式包括:

# 默认走 HTTPS
PRELUDE_DSN=prelude://API_KEY@default?sender=SENDER_ID

# 显式启用 HTTPS(默认即如此)
PRELUDE_DSN=prelude://API_KEY@default?sender=SENDER_ID&ssl=1

# 关闭 SSL,走明文 HTTP(适用于调试、内网代理等场景)
PRELUDE_DSN=prelude://API_KEY@default?sender=SENDER_ID&ssl=0

其中 ssl=0 即 CHANGELOG 所述 "send requests over plain HTTP" 的用法。需要提醒的是,明文 HTTP 会暴露 API Key(以 Authorization: Bearer 头传递),仅应在受控环境(如本地代理、调试)中使用。

四、发送消息:PreludeOptions 与模板必填约束

Prelude Notify API 强制要求 template_id,必须通过 PreludeOptions 提供。基础用法(示例源自 Prelude README):

use Symfony\Component\Notifier\Message\SmsMessage;
use Symfony\Component\Notifier\Bridge\Prelude\PreludeOptions;

$options = (new PreludeOptions())
    ->templateId('template_01k8xxxxxxxxxxxxx') // 必填
    ->variables([
        'order_id' => '12345',
        'amount' => '$49.99',
    ])
    // 可选参数
    // ->from('MySenderID')
    // ->locale('fr-FR')
    // ->callbackUrl('https://example.com/webhook')
    // ->preferredChannel('whatsapp')
;

$message = (new SmsMessage('+33612345678', 'Subject (ignored)'))
    ->options($options);

$notifier->send($message);

其中 SmsMessage 的第二个参数(正文/主题)在 Prelude 场景下会被忽略——因为消息内容完全由模板决定。若配置了 DSN 层的 sender,则优先以 DSN 为准;也可以在选项层用 from() 覆盖。

选项全参数说明

PreludeOptions 实现了 MessageOptionsInterface,所有方法采用链式调用(返回 static),内部以蛇形命名(snake_case)存储于 $options 数组。各参数含义如下:

方法 存储键 类型 必填 说明
templateId(string) template_id string 是 模板标识符
variables(array) variables array 否 模板变量的键值对
from(string) from string 否 Sender ID(发件人标识)
locale(string) locale string 否 BCP-47 格式的本地化字符串,如 fr-FR
expiresAt(string) expires_at string 否 消息过期时间(RFC3339 格式)
scheduleAt(string) schedule_at string 否 定时投递时间(RFC3339 格式)
callbackUrl(string) callback_url string 否 投递事件回调 URL
correlationId(string) correlation_id string 否 用户自定义关联标识
preferredChannel(string) preferred_channel string 否 首选渠道:'sms' 或 'whatsapp'

方法名与存储键的映射可逐一对照 PreludeOptions.php 源码确认(如 templateId() 写入 template_id、callbackUrl() 写入 callback_url)。

五、源码级原理:请求如何被构造与发送

传输层的发送流程

PreludeTransport::doSend()(见 PreludeTransport.php)完整流程如下:

  1. 消息类型校验:仅支持 SmsMessage,否则抛出 UnsupportedMessageTypeException;
  2. 选项类型校验:若消息带选项但不是 PreludeOptions,抛出 UnsupportedOptionsException;
  3. 构造请求体:以 'to' => $message->getPhone() 为起点;若传输层配置了 sender,则追加 'from';随后合并 PreludeOptions::toArray() 的全部选项;
  4. 模板校验:若合并后的请求体不含 template_id,抛出 LogicException,错误信息为 The "template_id" option is required for the "Symfony\Component\Notifier\Bridge\Prelude\PreludeTransport" transport.(该行为被 PreludeTransportTest::testSendWithoutTemplateIdThrowsLogicException 覆盖);
  5. 发起请求:POST {scheme}://{endpoint}/v2/notify,携带 json 请求体与 Authorization: Bearer {apiKey} 头;
  6. 错误处理:网络层异常或非 200/201 状态码均包装为 TransportException,并从响应体提取 message 字段作为错误详情(测试用例 testSendWithErrorResponseThrowsTransportException 验证了 400 响应抛出 Unable to send the SMS: bad request);
  7. 成功处理:从响应 JSON 中读取 id 写入 SentMessage::setMessageId(),供后续追踪投递状态。

传输的字符串表示

PreludeTransport::__toString() 返回 prelude://{endpoint}?sender={sender} 形式。在 PreludeTransportTest::toStringProvider 中,设置了自定义主机 host.test 的传输实例被序列化为 prelude://host.test?sender=0611223344。

工厂的 DSN 解析

PreludeTransportFactory 的解析逻辑(见 PreludeTransportFactory.php)与 PreludeTransportFactoryTest 中的用例相互印证:

  • createProvider:prelude://apiKey@host.test?sender=0611223344 可被创建;
  • supportsProvider:仅 prelude:// scheme 返回 true;
  • incompleteDsnProvider:缺少 API Key 的 prelude://default?sender=... 视为不完整 DSN(由 AbstractTransportFactory::getUser() 抛 IncompleteDsnException,见 AbstractTransportFactory.php);
  • unsupportedSchemeProvider:其他 scheme 抛出 UnsupportedSchemeException。

传输注册

框架层通过 notifier_transports.php 与 Transport.php 完成工厂的自动发现注册。这意味着只要安装了 symfony/prelude-notifier 包,prelude:// scheme 即可被 Notifier 组件自动识别并使用。

六、消息类型限制与注意事项

  • 仅支持短信消息:传输的 supports() 方法只接受 SmsMessage;向该传输发送 ChatMessage 或其他消息类型会被拒绝(PreludeTransportTest::unsupportedMessagesProvider 中明确以 ChatMessage 与 DummyMessage 作为不支持的消息类型用例);
  • 模板驱动内容:SmsMessage 的正文参数被忽略,所有内容渲染由 Prelude 侧模板完成,因此必须在每次发送时通过 PreludeOptions 指定 template_id;
  • 渠道偏好:可通过 preferredChannel('whatsapp') 将消息引导至 WhatsApp 渠道;
  • 环境要求:桥接要求 PHP >= 8.4.1,且依赖 symfony/notifier ^8.2,请在满足版本的 Symfony 项目中接入。

七、总结

Prelude 桥接是 Symfony Notifier 体系中面向模板化短信/WhatsApp 通知的官方通道。从 8.1 的首次引入到 8.2 的 ssl 选项,其演进始终围绕 DSN 驱动、选项链式配置、模板必填校验三大设计。理解 PreludeTransport、PreludeTransportFactory 与 PreludeOptions 三者协作,即可在任意 Symfony 8.x 应用中快速接入 Prelude Notify,实现稳定、可追踪的模板消息投递。

登录后查看全文
symfony