Symfony Mailer:捕获MessageSent事件时邮件上下文数据丢失
Symfony 6.3中SentMessageEvent无法获取TemplatedEmail上下文的解决方案
问题原因
Symfony在处理TemplatedEmail时,会在**邮件渲染完成后(发送前)**自动清空上下文、模板路径等数据——因为此时模板已经被编译成最终的HTML/文本内容,原始的上下文和模板信息已经完成使命,框架默认会清理这些冗余数据以节省内存,这是设计上的正常行为。这也是为什么你在MessageEvent(触发于渲染前)能拿到完整上下文,而SentMessageEvent(触发于发送后)里上下文为空的原因。
解决方法
方法1:通过MessageEvent提前存储上下文到邮件元数据
利用MessageEvent在渲染前触发的时机,把上下文数据存入邮件的元数据中,后续在SentMessageEvent里直接读取:
修改你的监听器代码:
public function onMessage(MessageEvent $event): void { $message = $event->getMessage(); if ($message instanceof \Symfony\Bridge\Twig\Mime\TemplatedEmail) { // 将上下文存入邮件元数据 $message->setMetadata('email_context', $message->getContext()); } } public function onMessageSent(SentMessageEvent $event): void { $originalMessage = $event->getMessage()->getOriginalMessage(); if ($originalMessage instanceof \Symfony\Bridge\Twig\Mime\TemplatedEmail) { // 从元数据中读取上下文 $context = $originalMessage->getMetadata('email_context'); // 这里可以使用$context了 dump($context); } }
方法2:添加自定义邮件头存储上下文
如果元数据方式不适用,也可以把上下文序列化为字符串后存入自定义邮件头,发送后再解析:
public function onMessage(MessageEvent $event): void { $message = $event->getMessage(); if ($message instanceof \Symfony\Bridge\Twig\Mime\TemplatedEmail) { // 序列化上下文为JSON字符串 $contextJson = json_encode($message->getContext()); // 添加自定义邮件头 $message->getHeaders()->addTextHeader('X-Email-Context', $contextJson); } } public function onMessageSent(SentMessageEvent $event): void { $originalMessage = $event->getMessage()->getOriginalMessage(); if ($originalMessage instanceof \Symfony\Bridge\Twig\Mime\TemplatedEmail) { $contextHeader = $originalMessage->getHeaders()->get('X-Email-Context'); if ($contextHeader) { // 解析JSON获取上下文 $context = json_decode($contextHeader->getValue(), true); dump($context); } } }
方法3:自定义TemplatedEmail子类(不推荐)
通过重写TemplatedEmail的updateBody方法,阻止框架清空上下文。这种方法可能会影响框架的默认行为,仅在特殊场景下使用:
- 创建自定义类:
namespace App\Mime; use Symfony\Bridge\Twig\Mime\TemplatedEmail as BaseTemplatedEmail; class TemplatedEmail extends BaseTemplatedEmail { protected function updateBody(): void { // 调用父类方法完成模板渲染 parent::updateBody(); // 注释掉父类中清空上下文和模板的代码 // 父类原方法最后会执行:$this->context = []; $this->htmlTemplate = null; $this->textTemplate = null; // 这里我们不执行这些清空操作,保留上下文 } }
- 发送邮件时使用这个自定义类:
use App\Mime\TemplatedEmail; // ... $email = (new TemplatedEmail()) ->to('at@example.com') ->from('from@example.fr') ->subject('My subject') ->htmlTemplate('email/email.html.twig') ->context([ 'data' => [ 'key' => 'value' ] ]);
推荐方案
优先选择方法1或方法2,这两种方式利用了Symfony的扩展机制,不会破坏框架的默认行为,兼容性更好。方法3仅在必须保留原始TemplatedEmail对象结构时考虑使用。
内容的提问来源于stack exchange,提问作者beta-developper
相关产品推荐
相关产品推荐

