根本原因是队列工作进程未运行或邮件任务未正确入队;需执行php yii queue/listen启动常驻进程,任务类须继承yii\base\Object并实现execute()方法,内部重建Mailer实例,避免依赖Yii::$app。

Yii2 中用 yii\queue 发邮件为什么没反应?
根本原因通常是队列驱动没真正跑起来,或者邮件任务没正确推入队列。Yii 的 yii\queue 本身不执行任务,它只负责存任务、发信号;你得单独启一个工作进程来消费队列(比如用 php yii queue/listen),否则任务永远卡在数据库或 Redis 里。
常见错误现象:queue push 返回 ID,但邮箱收不到信,日志里也没报错;查数据库发现 queue 表有记录但 done_at 始终为空。
- 确认已安装并配置好具体驱动(如
db或redis),不能只装扩展不配components['queue'] - 运行
php yii queue/listen后别关终端——它是个常驻进程,不是“执行一次就完” - 若用
db驱动,确保yii\queue\db\Queue对应的表已通过yii migrate --migrationPath=@yii/queue/migrations创建
怎么写一个可进队列的邮件任务类?
必须继承 yii\base\Object 并实现 execute() 方法,且不能依赖 $this->mailer 这类 Yii 组件实例——因为队列任务是在无 Web 上下文的 CLI 进程中执行的,Yii::$app 不可用。
典型做法是把邮件参数序列化传入,任务类内部重新构建 Mailer 实例或直接调用底层发送逻辑。
- 不要在构造函数里调用
Yii::$app->mailer,改在execute()里用配置数组初始化新实例 - 参数尽量精简:收件人、主题、文本内容、是否 HTML,避免传整个
ActiveRecord对象(序列化可能失败或超限) - 示例结构:
class SendEmailJob extends \yii\base\Object implements \yii\queue\JobInterface { public $to; public $subject; public $html; <pre class="brush:php;toolbar:false;">public function execute($queue) { $mailer = \Yii::createObject([ 'class' => 'yii\swiftmailer\Mailer', 'transport' => ['class' => 'Swift_SmtpTransport', /* ... */], ]); $mailer->compose() ->setTo($this->to) ->setSubject($this->subject) ->setHtmlBody($this->html) ->send(); }}
为什么发邮件时提示 Class 'Swift_SmtpTransport' not found?
这是环境不一致导致的:Web 请求时自动加载了 swiftmailer/swiftmailer,但 CLI 下 Composer autoloader 没被正确引入,尤其在某些部署方式(如 Supervisor 管理队列进程)中容易漏掉 vendor/autoload.php。
- 检查
php yii queue/listen执行时的 PHPinclude_path和当前工作目录,确保它和 Web 入口一样能加载到vendor/autoload.php - 如果用的是自定义入口脚本启动队列,务必在开头加上
require __DIR__ . '/vendor/autoload.php'; - 避免在任务类中用
new Swift_SmtpTransport(),改用Yii::createObject()+ 配置数组,让 DI 容器统一管理依赖
队列发邮件比同步慢很多,怎么优化?
慢通常不是因为队列本身,而是任务设计不合理:比如每次发一封都重建 SMTP 连接、没启用连接复用、或者把大附件塞进序列化参数里。
- SMTP 连接默认是短连,可在
transport配置中加'streamOptions' => ['ssl' => ['verify_peer' => false]](仅测试环境)并开启'pingThreshold' => 30复用连接 - 附件不要 base64 后塞进任务属性,改用临时文件路径 + 任务内读取,或提前上传到对象存储后传 URL
- 批量发送别用“循环 push 单封”,改用一个任务处理多封(如
public $batch = []),减少队列 I/O 次数
队列发邮件最易被忽略的点是:任务类的生命周期完全独立于 Web 请求,所有外部依赖(数据库连接、邮件服务、文件系统)都得在 execute() 内显式准备,而不是想当然地复用当前请求上下文里的东西。


















