应捕获swift_transportexception并打印$e->getprevious()->getmessage(),因getmessage()仅显示笼统的“connection could not be established”,真实错误(如tls握手失败、ssl证书验证失败、fsockopen被拒)均藏于getprevious()中。

直接捕获 Swift_TransportException 并打印 $e->getPrevious()->getMessage(),否则你看到的永远是 “Connection could not be established” 这种废话。
为什么默认错误信息没用
Yii2 的 send() 失败时抛出的 Swift_TransportException,getMessage() 只返回笼统的连接失败提示,真正关键的底层原因(比如 TLS 握手失败、fsockopen 被拒、证书验证错误)全藏在 $e->getPrevious() 里。不挖这一层,你只能靠猜。
- 常见现象:日志里只写
Swift_TransportException: Connection could not be established with host smtp.qq.com,但 telnet 能通、密码也对,就是发不出 - 真实原因往往在
$e->getPrevious()->getMessage():例如stream_socket_enable_crypto(): SSL operation failed with code 1. OpenSSL Error messages: error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed - PHP 的
openssl扩展未启用、CA 证书路径不对、或服务器禁用了 TLSv1.2,都会导致这个深层错误
怎么在控制器里快速定位
别等用户报错,自己先在测试逻辑里加 try-catch,强制暴露原始错误:
try {
$sent = Yii::$app->mailer->compose()
->setTo('test@example.com')
->setFrom(['no-reply@yoursite.com' => 'Site'])
->setSubject('Test')
->setHtmlBody('<p>OK</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2519" title="Yii Framework 2.0.51"><img
src="https://img.php.cn/upload/manual/001/503/042/6a6b03d191dbf935.png" alt="Yii Framework 2.0.51" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/gongju/2519" title="Yii Framework 2.0.51" class="overflowclass">Yii Framework 2.0.51</a>
<p class="overflowclass">Yii Framework 2.0.51 官方 Basic 应用模板,适合旧项目兼容、升级验证和开发测试。</p>
</div>
<a rel="nofollow" href="/xiazai/gongju/2519" title="Yii Framework 2.0.51" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>')
->send();
var_dump($sent);
} catch (\Swift_TransportException $e) {
// 关键:必须打 getPrevious()
$prev = $e->getPrevious();
echo '<pre class="brush:php;toolbar:false;">';
echo 'Main: ' . $e->getMessage() . "\n";
echo 'Cause: ' . ($prev ? $prev->getMessage() : 'none') . "\n";
echo '';
die();
}
- 执行后页面会直接输出两行——上面是 Swift 层包装的假消息,下面是 PHP socket 或 OpenSSL 真实报的错
- 如果
$prev为空,说明问题在 Swift 配置本身(比如transport缺字段),不是网络或加密层 - 不要依赖
Yii::$app->errorHandler->exception($e),它会二次封装,反而掩盖getPrevious()
日志里怎么留痕才有效
仅靠临时 dump 不够,生产环境得让错误自动进日志,且带完整上下文:
- 在
config/main.php的'log' => ['targets' => [...]]里加一条:['class' => 'yii\log\FileTarget', 'levels' => ['error'], 'categories' => ['yii\swiftmailer\Mailer']]
- 但光这样还不够——SwiftMailer 默认不把
getPrevious()写进日志。你需要补个自定义 formatter,在runtime/logs/app.log里搜Swift_TransportException后,手动打开对应时间戳的日志行,看有没有连带的SSL routines或fsockopen字样 - 确认
php.ini中log_errors = On且error_log路径可写,否则日志根本不会落盘
容易被忽略的三个硬性前提
再完整的错误捕获也救不了基础环境缺失:
-
php -m | grep -E 'openssl|sockets'必须有输出,缺任一扩展,Swift_SmtpTransport初始化就崩,错误甚至进不到send()阶段 -
useFileTransport => false和transport配置必须共存;如果某处 config(比如main-local.php)偷偷设了useFileTransport => true,整个 SMTP 链路直接跳过,send()返回 true 但邮件根本没发——这种静默失败比报错更难查 - 云服务器(阿里云/腾讯云)默认封 25/465/587 端口,telnet 测试必须在目标机器上执行,不能只在本地测;安全组和防火墙规则要双向检查
最麻烦的是证书验证失败类错误,它不报端口不通,也不说密码错,就卡在 TLS 握手,而 getPrevious() 是唯一能把它揪出来的绳子。










