You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Symfony3中Swift_Mailer发送事件监听器配置及附件删除问题

如何为SwiftMailer发送事件添加EventListener并安全删除附件文件?

你遇到的问题很典型:在邮件发送完成后删除临时附件,但当前的sendPerformed事件触发时机太早,导致SwiftMailer还在读取文件时就被删除,抛出Swift_IoException异常。下面我来帮你分析原因并给出两种可靠的解决方案:

问题根源

SwiftMailer默认以流方式读取附件文件,sendPerformed事件是在邮件的“发送动作”触发后立即执行,但此时传输层(比如SMTP连接)可能还在发送附件的数据流,文件资源并未完全释放。这时候删除文件,会导致后续读取操作失败。


解决方案1:将附件加载到内存(适合小文件)

如果你的附件文件不大,可以直接把文件内容读取到内存中创建附件,这样文件可以在创建附件后立即删除,完全不需要依赖事件监听器:

// 读取临时文件内容
$fileContent = file_get_contents($path);
// 创建内存中的附件(替代从文件路径创建)
$attachment = new \Swift_Attachment(
    $fileContent,
    basename($path), // 附件显示的文件名
    'text/plain'    // 文件的MIME类型,根据你的实际情况调整
);

// 组装邮件
$message = (new \Swift_Message('VAT checking result !'))
    ->setFrom('vah@gmail.com')
    ->setTo($vat->getEmail())
    ->setBody('Hello, ...', 'text/plain')
    ->attach($attachment);

// 现在可以安全删除临时文件了
unlink($path);

// 发送邮件
$mailer = $this->container->get('mailer');
$mailer->send($message);

这种方式简单直接,不需要额外的监听器,适合小体积的附件。


解决方案2:监听Transport停止事件(适合大文件)

如果附件是大文件,加载到内存会占用过多资源,这时候可以修改监听器,监听transportStopped事件——这个事件会在整个邮件传输完全结束、所有文件流和连接都释放后触发,此时删除文件绝对安全。

步骤1:修改监听器类

namespace AppBundle\Listener;

use Swift_Events_TransportListener;
use Swift_Events_TransportEvent;

class SendEmailListener implements Swift_Events_TransportListener
{
    private $pathToFile;

    public function setPathToFile($path)
    {
        $this->pathToFile = $path;
    }

    // 以下两个方法是接口要求的空实现
    public function beforeTransportStarted(Swift_Events_TransportEvent $evt) {}
    public function transportStarted(Swift_Events_TransportEvent $evt) {}

    // 关键:在传输停止后删除文件
    public function transportStopped(Swift_Events_TransportEvent $evt)
    {
        // 先检查文件存在,避免报错
        if ($this->pathToFile && file_exists($this->pathToFile)) {
            unlink($this->pathToFile);
            // 清空路径,防止重复删除
            $this->pathToFile = null;
        }
    }
}

步骤2:保持原有的监听器注册配置

你的服务配置不需要修改,依然是:

app.service.send_email_listener:
    class: AppBundle\Listener\SendEmailListener
    public: true
    tags:
        - { name: swiftmailer.plugin }

步骤3:发送邮件的代码不变

你原来的发送逻辑可以保留,监听器会自动在传输结束后删除文件:

file_put_contents($path, implode(";\n", $result));

$message = (new \Swift_Message('VAT checking result !'))
    ->setFrom('vah@gmail.com')
    ->setTo($vat->getEmail())
    ->setBody('Hello, ...', 'text/plain')
    ->attach(\Swift_Attachment::fromPath($path));

$mailer = $this->container->get('mailer');
$listener = $this->container->get('app.service.send_email_listener');
$listener->setPathToFile($path);
$mailer->registerPlugin($listener);
$mailer->send($message);

额外注意事项

  • 如果使用了SwiftMailer的邮件队列(Spool),邮件不会立即发送,这时候需要等到队列被消费时再删除文件,你可以结合队列的事件或者在消费逻辑中处理删除。
  • 每次发送邮件后,建议重置监听器的pathToFile属性,避免多次发送时文件路径被覆盖。

内容的提问来源于stack exchange,提问作者Andrew Vakhniuk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 10:13:58