Customizing the PHP Error Handler
by Tony MarstonTonyMarston.net
Wednesday, 21st June 2006
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:
- Trap all errors whether triggered by PHP or a specific function call.
- Filter all errors depending on their error level.
- Output a formatted error message to the user.
- Send an email to the system administrator.
- Write the error details to a separate log file.
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. function errorHandler ($errno, $errstr, $errfile, $errline, $errcontext)
{
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.
switch ($errno)
{
case E_USER_WARNING:
case E_USER_NOTICE:
case E_WARNING:
case E_NOTICE:
case E_CORE_WARNING:
case E_COMPILE_WARNING:
break;
case E_USER_ERROR:
case E_ERROR:
case E_PARSE:
case E_CORE_ERROR:
case E_COMPILE_ERROR:
{
case E_USER_WARNING:
case E_USER_NOTICE:
case E_WARNING:
case E_NOTICE:
case E_CORE_WARNING:
case E_COMPILE_WARNING:
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 (eregi('^(sql)$', $errstr)) {
$MYSQL_ERRNO = mysql_errno();
$MYSQL_ERROR = mysql_error();
$errstr = "MySQL error: $MYSQL_ERRNO : $MYSQL_ERROR";
} else {
$query = NULL;
} // if
if (eregi('^(sql)$', $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 = "
" .date('Y-m-d H:i:s') ."
\n";
" .date('Y-m-d H:i:s') ."
\n";
Next we include the contents of $errno and $errstr.
$errorstring .= "
Fatal Error: $errstr (# $errno).
\n";
Fatal Error: $errstr (# $errno).
\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 .= "
SQL query: $query
\n";
SQL query: $query
\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 .= "
Error in line $errline of file '$errfile'.
\n";
$errorstring .= "
Script: '{$_SERVER['PHP_SELF']}'.
\n";
Error in line $errline of file '$errfile'.
\n";
$errorstring .= "
Script: '{$_SERVER['PHP_SELF']}'.
\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 .= "
Object/Class: '$classname', Parent Class: '$parentclass'.
\n";
} // if
} // if
if (is_object($errcontext['this'])) {
$classname = get_class($errcontext['this']);
$parentclass = get_parent_class($errcontext['this']);
$errorstring .= "
Object/Class: '$classname', Parent Class: '$parentclass'.
\n";
} // if
} // if
This next group of lines will send the error message to the client's browser.
echo "
This system is temporarily unavailable
\n";
echo "
The following has been reported to the administrator:
\n";
echo "\n$errorstring\n";
This system is temporarily unavailable
\n";
echo "
The following has been reported to the administrator:
\n";
echo "\n$errorstring\n";
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);
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
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 mysql_errno() and mysql_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
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.Options:
Printer Friendly
Email Friend
About The Author:
I have been a software engineer, both designing and developing, since 1977. I have worked with a variety of 2nd, 3rd and 4th generation languages on a mixture of mainframes, mini- and micro-computers. I have worked with flat files, indexed files, hierarchical databases, network databases and relational databases. The user interfaces have included punched card, paper tape, teletype, block mode, CHUI, GUI and web. I have written code which has been procedural, model-driven, event-driven, component-based and object oriented. I have built software using the 1-tier, 2-tier, 3-tier and Model-View-Controller (MVC) architectures. After working with COBOL for 16 years I switched to UNIFACE in 1993, starting with version 5, then progressing through version 6 to version 7. In the middle of 2002 I decided to teach myself to develop web applications using PHP and MySQL.
I have been a software engineer, both designing and developing, since 1977. I have worked with a variety of 2nd, 3rd and 4th generation languages on a mixture of mainframes, mini- and micro-computers. I have worked with flat files, indexed files, hierarchical databases, network databases and relational databases. The user interfaces have included punched card, paper tape, teletype, block mode, CHUI, GUI and web. I have written code which has been procedural, model-driven, event-driven, component-based and object oriented. I have built software using the 1-tier, 2-tier, 3-tier and Model-View-Controller (MVC) architectures. After working with COBOL for 16 years I switched to UNIFACE in 1993, starting with version 5, then progressing through version 6 to version 7. In the middle of 2002 I decided to teach myself to develop web applications using PHP and MySQL.
