Tony Marston's Blog About software development, PHP and OOP

Customising the PHP error handler

Posted on 12th June 2003 by Tony Marston

Amended on 15th April 2014

Intended Audience
Introduction
Creating the error handler
Calling the error handler
Summary
Amendment History
Comments

Intended Audience

This tutorial is intended for the novice or intermediate PHP programmer. Basic knowledge of creating and using functions is assumed.

Introduction

One of the last areas that developers seem to deal with in their code is error handling. I am not talking here about errors with user input where it is a simple matter to display an error message and ask for new input, I am taking about the kind of error over which the user has absolutely no control and which may cause the current PHP script to stop running. In this situation you as a developer need to know some details about the error otherwise you will have a very difficult time in locating the source of the error so that you can fix it.

Developers new to PHP often complain there there is no proper error handling in the language. This is utter nonsense. All the functionality is there, you just have to customise it to your specific requirements.

In this tutorial I will show to how to create an error handler that will:

Creating the error handler

This is done by using the set_error_handler() function. With this you supply the name of the function you want to be called when any errors are triggered. You then create a function of this name to deal with these errors. In the following code snippets I shall use the name errorHandler.

set_error_handler('errorHandler');

function errorHandler ($errno, $errstr, $errfile, $errline, $errcontext)
{

Here I am using the switch() statement to take action depending on the value of $errno. Anything less than E_USER_ERROR will be ignored and processing will continue.

Note that by defining your own error handler the standard PHP error handler is completely bypassed and any settings for error_reporting() are completely ignored. This is why we are checking for all possible error levels, not just fatal errors. Note that if the error is non-fatal it will populate certain global variables (such as $php_errormsg) before returning so that this information is available immediately after the line of code which generated the error.

   switch ($errno)
   {
      case E_USER_WARNING:
      case E_USER_NOTICE:
      case E_WARNING:
      case E_NOTICE:
      case E_CORE_WARNING:
      case E_COMPILE_WARNING:
      case E_STRICT;             // since PHP 5
      case E_RECOVERABLE_ERROR;  // since PHP 5.2.0
      case E_DEPRECATED;         // since PHP 5.3.0
      case E_USER_DEPRECATED;    // since PHP 5.3.0
         // save error details before returning
         $GLOBALS['php_errorno']   = $errno;
         $GLOBALS['php_errormsg']  = $errstr;
         $GLOBALS['php_errorfile'] = $errfile;
         $GLOBALS['php_errorline'] = $errline;
         break;
      case E_USER_ERROR:
      case E_ERROR:
      case E_PARSE:
      case E_CORE_ERROR:
      case E_COMPILE_ERROR:

Here I am picking up the last SQL query from a global variable. I am them checking the contents of $errstr to see if this error was triggered with the string 'SQL' so I know when to include some additional information.

         global $query;
   
         if (preg_match('/^(sql)$/i', $errstr)) {
            $MYSQL_ERRNO = mysql_errno();
            $MYSQL_ERROR = mysql_error();
            $errstr = "MySQL error: $MYSQL_ERRNO : $MYSQL_ERROR";
         } else {
            $query = NULL;
         } // if

Now we can start constructing an error message. I personally like to start with the current date and time.

         $errorstring = "<h2>" .date('Y-m-d H:i:s') ."</h2>\n";

Next we include the contents of $errno and $errstr.

         $errorstring .= "<p>Fatal Error: $errstr (# $errno).</p>\n";

If the error was triggered by an SQL problem I have found it of enormous benefit to include the actual query string in the error message.

         if ($query) $errorstring .= "<p>SQL query: $query</p>\n";

These next lines will identify the filename and line number where the error was triggered, plus the name of the currently executing script.

         $errorstring .= "<p>Error in line $errline of file '$errfile'.</p>\n";
         $errorstring .= "<p>Script: '{$_SERVER['PHP_SELF']}'.</p>\n";

These next lines of code show you how it is possible to extract some more information about the context of the current error. In this case if the context is an object it will extract the name of the class and the name of the parent class (if it is a derived class).

         if (isset($errcontext['this'])) {
            if (is_object($errcontext['this'])) {
               $classname = get_class($errcontext['this']);
               $parentclass = get_parent_class($errcontext['this']);
               $errorstring .= "<p>Object/Class: '$classname', Parent Class: '$parentclass'.</p>\n";
            } // if
         } // if

This next group of lines will send the error message to the client's browser.

         echo "<h2>This system is temporarily unavailable</h2>\n";
         echo "<p>The following has been reported to the administrator:</p>\n";
         echo "<b><font color='red'>\n$errorstring\n</b></font>";

This next line of code will send the error details to the system administrator by email.

         error_log($errorstring, 1, $_SERVER['SERVER_ADMIN']);

Here I am appending the error details to a disk file with the extension '.html' so that I can easily view its contents with my browser. This means that I do not have to wait for the email to arrive to obtain the details of the error.

         $logfile = $_SERVER['DOCUMENT_ROOT'] .'/errorlog.html';
         error_log($errorstring, 3, $logfile);

The final act is to terminate the current session then cease processing altogether.

         session_start();
         session_unset();
         session_destroy();
         die();
      default:
         break;
   } // switch
} // errorHandler

Calling the error handler

Certain types of error, such as trying to access a variable which has not yet been defined, will be triggered automatically by PHP. As these are at the E_NOTICE level they will fall through the error handler and allow processing to continue. It would be possible to do something with these errors, such as writing them out to a log file, if you so desired.

It is also possible to trigger the error handler by using the trigger_error() function. Here for example we are using the simple error string 'SQL' to force the error handler to extract the contents of mysqli_errno() and mysqli_error().

   $result = mysql_query($query, $dbconnect) or trigger_error("SQL", E_USER_ERROR);

For non-sql errors a specific error string should be supplied.

   if ($divisor == 0) { 
      trigger_error ("Cannot divide by zero", E_USER_ERROR);
   } // if
   if (!function_exists('xslt_create')) {
      trigger_error('XSLT functions are not available.', E_USER_ERROR);
   } // if

Summary

As you can see the steps required to create an error handler are quite small, but whenever an error occurs you can now be supplied with all the relevant details so you can track it down and fix it more easily.


Amendment history:

15 Apr 2014 Updated the case statement to include additional error levels which appeared in PHP5, and to populate certain global variables before returning from a non-fatal error.

counter