Fatal error when passing parameter by reference

93 Views Asked by At

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
1

There are 1 best solutions below

3
minitauros On BEST ANSWER

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->Debugoutput to be a callable that appends an item to $debug_log. Now, when at the end of your function you set $debug_log to be a string ($debug_log = !$sent etc.), during that setting of a string, it might happen that during the implode() that happens when the string concatenation happens for some reason the $mail->Debugoutput is 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.