In our PHP app there is a function to send a mail with PHPMailer while collecting the logs of the sending process:
function sendWithLog(PHPMailer $mail, &$debug_log, $log_level = PHPMailer_SMTP::DEBUG_CONNECTION) {
$debug_log = [];
$mail->SMTPDebug = $log_level ?: PHPMailer_SMTP::DEBUG_CONNECTION;
$mail->Debugoutput = function($message) use(&$debug_log) {
$debug_log[] = $message;
};
$sent = $mail->send();
$debug_log = !$sent
? $mail->ErrorInfo
. ($debug_log ? "\n" . implode("\n", $debug_log) : "")
: "";
return $sent;
}
So, the variable $debug_log, passed by reference, should first be initialized as an empty array, and at the end it is made into a string.
If the function is called again, passing as $debug_log the $debug_log of the previous call, I see this error:
PHP Fatal error: Uncaught Error: [] operator not supported for strings
However, the variable should still be initialized as empty array before being bound to the closure with the use() statement, so I really do not understand how this error can happen.
I tried reproducing the behaviour with a simplified version of the code here, but it doesn't seem to trigger the error.
EDIT:
I have solved the problem by changing the function like this:
function sendWithLog(PHPMailer $mail, &$debug_log, $log_level = PHPMailer_SMTP::DEBUG_CONNECTION) {
$messages = []; // <-- different variable for collecting the log messages!
$mail->SMTPDebug = $log_level ?: PHPMailer_SMTP::DEBUG_CONNECTION;
$mail->Debugoutput = function($message) use(&$messages) {
$messages[] = $message;
};
$sent = $mail->send();
$debug_log = !$sent
? $mail->ErrorInfo
. ($messages ? "\n" . implode("\n", $messages) : "")
: "";
return $sent;
}
However, I am still wondering how it could trigger the error in the first place.
An example of usage is the following:
// $users -> query result from database
while($user = $users->fetch()) {
// build mail and then
$sent = sendWithLog($mail, $mail_log);
// here $mail_log should be initialized to NULL the first time by the PHP engine
// and in subsequent calls it is the log string of the previous call which should be
// reset to [] first thing inside the function call
if(!$sent) {
error_log("ERROR SENDING MAIL: $mail_log");
}
}
EDIT 2
Here is a sample stack trace:
PHP Fatal error: Uncaught Error: [] operator not supported for strings in /opt/agews64/www/includes/class/AGEws/Mail.php:134
Stack trace:
0 /opt/agews64/www/includes/class/PHPMailer/SMTP.php(220): AGEws_Mail::{closure}('CLIENT -> SERVE...', 1)
1 /opt/agews64/www/includes/class/PHPMailer/SMTP.php(991): PHPMailer_SMTP->edebug('CLIENT -> SERVE...', 1)
2 /opt/agews64/www/includes/class/PHPMailer/SMTP.php(885): PHPMailer_SMTP->client_send('QUIT\r\n')
3 /opt/agews64/www/includes/class/PHPMailer/SMTP.php(827): PHPMailer_SMTP->sendCommand('QUIT', 'QUIT', 221)
4 /opt/agews64/www/includes/class/PHPMailer.php(1721): PHPMailer_SMTP->quit()
5 /opt/agews64/www/includes/class/PHPMailer.php(670): PHPMailer->smtpClose()
6 /opt/agews64/www/includes/class/AGEws/Mail.php(147): PHPMailer->__destruct()
7 /opt/agews64/www/includes/class/AGEws/Mail.php(118): AGEws_Mail::sendWithLog(NULL, 'SMTP Error: Dat...', 3)
8 /opt/agews64/www/includes/class/AGEws/Cron/Import.php(154): AGEws_Mail::prepareAndSendWithLog('SMTP Error: Dat...', Array, 'Risu in /opt/agews64/www/includes/class/AGEws/Mail.php on line 134
This was just too long for a comment, so let me type it here:
The only thing I can think of considering your example - and I'm unsure about the internals at work here, but let me try to explain anyway - is:
You set
$mail->Debugoutputto be a callable that appends an item to$debug_log. Now, when at the end of your function you set$debug_logto be a string ($debug_log = !$sentetc.), during that setting of a string, it might happen that during theimplode()that happens when the string concatenation happens for some reason the$mail->Debugoutputis called again (maybe during a__toString()?), which tries to append an array item to$debug_log, which is impossible, because it is being set to be a string. Hence the error.