Symfony Notifier Prelude 桥接组件实战:从 DSN 配置到模板短信发送的完整指南
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
sslDSN option to send requests over plain HTTP
即 8.2 起可通过 ssl 选项让请求走明文 HTTP。其底层实现链路如下:
- 工厂在 create() 末尾调用
->setSsl($this->getSsl($dsn)); AbstractTransportFactory::getSsl()读取dsn->getOption('ssl'),返回null(未设置)、true或false(见 AbstractTransportFactory.php);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)完整流程如下:
- 消息类型校验:仅支持
SmsMessage,否则抛出UnsupportedMessageTypeException; - 选项类型校验:若消息带选项但不是
PreludeOptions,抛出UnsupportedOptionsException; - 构造请求体:以
'to' => $message->getPhone()为起点;若传输层配置了 sender,则追加'from';随后合并PreludeOptions::toArray()的全部选项; - 模板校验:若合并后的请求体不含
template_id,抛出LogicException,错误信息为The "template_id" option is required for the "Symfony\Component\Notifier\Bridge\Prelude\PreludeTransport" transport.(该行为被 PreludeTransportTest::testSendWithoutTemplateIdThrowsLogicException 覆盖); - 发起请求:
POST {scheme}://{endpoint}/v2/notify,携带json请求体与Authorization: Bearer {apiKey}头; - 错误处理:网络层异常或非 200/201 状态码均包装为
TransportException,并从响应体提取message字段作为错误详情(测试用例 testSendWithErrorResponseThrowsTransportException 验证了 400 响应抛出Unable to send the SMS: bad request); - 成功处理:从响应 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,实现稳定、可追踪的模板消息投递。