PHPMailer在Cron Job中发送首封邮件后停止运行的问题
我之前也踩过类似的坑——浏览器里跑起来顺风顺水,一丢Cron里就直接“罢工”,大概率是CLI环境和Web环境的差异在搞鬼,给你几个排查和解决的方向:
1. 先抓出错误信息再说
CLI模式下默认可能不会显示错误,脚本出错直接静默终止,你根本不知道哪里出了问题。可以先给脚本加上错误输出配置:
error_reporting(E_ALL); ini_set('display_errors', 1); ini_set('log_errors', 1); ini_set('error_log', '/path/to/your/error.log'); // 换成你有权限写入的路径
或者直接在Cron命令里把输出重定向到日志文件,方便后续排查:
/usr/bin/php /path/to/your/script.php >> /var/log/mail_script.log 2>&1
执行完Cron任务后去看日志,就能清楚第一封邮件之后到底是哪里报错导致脚本终止了。
2. 搞定工作目录和路径问题
Cron默认的工作目录是执行用户的根目录(比如/home/your_user/),而不是你脚本所在的目录。如果脚本里用了相对路径加载PHPMailer或者其他依赖文件,CLI环境下就会找不到。
解决办法很简单:
- 在脚本开头强制切换到脚本所在目录:
chdir(__DIR__); // PHP 7+可用,旧版本用 dirname(__FILE__)
- 所有文件引用都改用绝对路径,比如:
require __DIR__ . '/vendor/autoload.php'; // 假设用Composer加载PHPMailer
3. 每次循环都重置PHPMailer实例
如果循环里复用同一个PHPMailer对象,可能会残留上一次的配置、缓存或者未释放的资源,导致后续发送失败。建议每次循环都重新初始化一个实例:
// 把通用配置(比如SMTP服务器信息)放在循环外 $smtpHost = 'smtp.example.com'; $smtpUser = 'your@mail.com'; $smtpPass = 'your_password'; while ($row = $result->fetch_assoc()) { // 每次循环新建PHPMailer实例 $mail = new PHPMailer\PHPMailer\PHPMailer(true); try { // 配置SMTP参数 $mail->isSMTP(); $mail->Host = $smtpHost; $mail->SMTPAuth = true; $mail->Username = $smtpUser; $mail->Password = $smtpPass; $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS; $mail->Port = 587; // 设置收件人、邮件内容等 $mail->setFrom('sender@example.com', 'Sender Name'); $mail->addAddress($row['email'], $row['name']); $mail->Subject = 'Your Report'; $mail->Body = "Hello {$row['name']}, here is your report content..."; $mail->send(); echo "Sent to {$row['email']} successfully\n"; } catch (Exception $e) { echo "Failed to send to {$row['email']}: {$mail->ErrorInfo}\n"; } // 销毁实例,释放资源 unset($mail); }
4. 检查CLI的PHP配置
Web服务器的PHP配置(php.ini)和CLI模式下的可能完全不一样,比如是否加载了openssl扩展(PHPMailer发SMTP邮件必须依赖这个),或者memory_limit设置过低。
你可以在终端执行以下命令检查:
# 查看openssl扩展是否启用 php -i | grep openssl # 查看CLI的PHP版本 php -v
如果openssl没启用,需要修改CLI对应的php.ini(通常在/etc/php/x.x/cli/php.ini)开启扩展;如果PHP版本和Web环境差异过大,也可能存在兼容性问题。
5. 避免超时或资源耗尽
如果客户列表很长,CLI脚本可能因为超时或内存不足终止(不过第一封就停的话概率较低,但可以提前预防):
set_time_limit(0); // 设置脚本不限时运行 ini_set('memory_limit', '256M'); // 根据实际需求调整内存限制
按照上面的步骤排查,基本能定位到问题——我之前就是因为路径没处理对,Cron下找不到PHPMailer的依赖文件,导致第一封之后脚本直接挂了。
内容的提问来源于stack exchange,提问作者Ken

