Mastodon

This morning I was faced with an issue with SwiftMailer where the PHP code was throwing an error message as it failed to connect with the mail server.

To solve it I first thought of using a try/catch, but obviously that wouldn’t work due to the fact that the function only throws a Warning, not an Exception.

The solution was quite straight forward…

  1. Create a custom error handler, to gracefully capture the warning and display a custom message
  2. Set the custom error handler before attempting to call the function causing the warning (in this case it was fsockopen)
  3. Reset the error handler to the default PHP one after calling the function.

Here’s an example of an error handler function you can use. I’m using Smarty for my output, but you can send your output to the users browser in any way you wish.

function errorHandler($errno,$errmsg,$errfile) {		
	   	//email yourself the error message and code
	   	$email	=	"An error {$erro} occured on page ".$_SERVER['REQUEST_URI'].", in the file {$errfile}.\r\n\r\nThe error is shown below:\r\n{$errmsg}";
	   	mail("[email protected]","PHP Error Notification",$email);
	   	
	   	//output a friendly message for the user	
	   	$wrapper	=	new SmartyTemplate(); 
	   	$msg		=	"Thank  you for your message.  Unfortunately we have been unable to send your email, please go back and resubmit the form or call us on 0800 123 456 directly.  Sorry for any inconvenience caused";   	
	   	$wrapper->assign("content", $msg);   		
	   	$wrapper->display("display_wrapper.tpl");
		exit();		
	}

The next step is to set our new error handler before the call to the function that causes the warning. As mentioned, my issue was with fsockopen in in SwiftMailer, but the fix is the same for anything. Here’s how I did it in Swift.
find the offending function (in Swift/Transport/StreamBuffer.php)

if(!$this->_stream = fsockopen($host, $this->_params['port'], $errno, $errstr, $timeout)) {

and add set the error handler before it.

set_error_handler("errorHandler");
    if(!$this->_stream = fsockopen($host, $this->_params['port'], $errno, $errstr, $timeout)) { 

As I only want my error handler to run for that particular function, and not for every function in the entire site, I just have to make sure I remove my custom error handler after the function.
Just add this after your function:

restore_error_handler();

Which will cause PHP to revert to it’s normal error handling method once it’s gotten back your function.

Extras
My usage of error handling was very specific to a single function. Of course you can have a site wide error handler instead which gracefully handles every PHP error/warning/notice within a single function. You could just declare the function a common file and then handle each kind of error independently. The PHP website has a great example of this:

function myErrorHandler($errno, $errstr, $errfile, $errline)
{
    if (!(error_reporting() & $errno)) {
        // This error code is not included in error_reporting
        return;
    }

    switch ($errno) {
    case E_USER_ERROR:
        echo "<b>My ERROR</b> [$errno] $errstr<br />\n";
        echo "  Fatal error on line $errline in file $errfile";
        echo ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
        echo "Aborting...<br />\n";
        exit(1);
        break;

    case E_USER_WARNING:
        echo "<b>My WARNING</b> [$errno] $errstr<br />\n";
        break;

    case E_USER_NOTICE:
        echo "<b>My NOTICE</b> [$errno] $errstr<br />\n";
        break;

    default:
        echo "Unknown error type: [$errno] $errstr<br />\n";
        break;
    }

    /* Don't execute PHP internal error handler */
    return true;
}

It would be quite easy to tweak that function so that it emailed you a log of the error (and/or stored it in the DB) and then redirected the user to a nice looking page notifying them that there’s an error, but you’re aware of it.

0
Would love your thoughts, please comment.x
()
x