PHP does not enjoy the same consistency in error (and exception) handling as other languages, mostly due to historical reasons and the lack of a formal specification. But, there are things that you can do to make error handling saner and easier to maintain.
PHP has multiple categories of errors and exceptions, each with its own handling semantics within the PHP interpreter. For our purposes, we can classify these into the following categories:
- Fatal errors, like out-of-memory and syntax errors,
E_ERROR,E_CORE_ERROR, andE_PARSE. - User errors, including
E_USER_ERROR,E_WARNING,E_PARSE, and all other non-fatal errors. - Exceptions (all subclasses of the
Exceptionclass).
Fatal errors are triggered from within the PHP interpreter and cannot be triggered by app code written in PHP. (It is possible, however, that some PHP extensions written in C may trigger fatal errors.)
User errors are largely deprecated in favor of exceptions, although they still get triggered by the PHP interpreter and some third-party libraries.
Exceptions behave the same way exceptions do in many other languages like Java, Python, and Ruby.
In an ideal world, all these categories would be condensed into one category, so you could apply the same rules and syntax to all of them. However, as you might have guessed, we do not live in an ideal world. Nonetheless, we will try to approximate it.
Fatal errorsThere is a lot of literature out there about how to handle fatal errors using
shutdown functions. Basically, set_error_handler() will
not work for fatal errors. Instead, you can use register_shutdown_function() and
pass it a function that performs some custom actions when fatal errors occur.
The problem is that, by the time the shutdown function is called, the call stack
is emptied, and you have no useful backtrace to work with, except for where in the file
the error has occurred. If you try to call debug_backtrace() in
your shutdown function, it will return an empty array.
One way to work around this is to log a global variable in the shutdown function that can help debug the problem. This global variable could be the session identifier, user identifier, or anything that can help correlate log lines produced by the shutdown function with other log lines produced within the normal run of the app.
Furthermore, following some examples, calling error_get_last()
means if any additional errors or warnings occur before the shutdown
function is called, these will overwrite the error that actually caused the
code to halt. Sadly, there is no way to avoid this, except to make sure
these errors do not happen in the first place. These usually happen if PHP is
incorrectly configured, or if you use poorly implemented third-party modules or C extensions
that throw errors on shutdown.
Much literature also exists about handling user errors, including in the PHP manual. However, most of the error handling solutions around suffer from two drawbacks:
- You have to maintain two separate handlers for errors and exceptions.
- You still cannot handle PHP errors like exceptions locally in the code. Instead, you have to rely on a global error handler.
The first drawback can be worked around by having both handlers simply call a central error handling facility instead of duplicating code. The following trick to wrap PHP errors as exceptions can be used to ameliorate the second drawback:
<?php
// These should usually be in your php.ini, but I'm putting it here to
// make sure this demonstration is portable across different configurations
ini_set('display_errors', 'stdout');
error_reporting(E_ALL);
function handle_error($errno, $errstr, $errfile, $errline) {
// Note that you could also check for a larger set of PHP errors like:
// $is_halting_error = $errno & (E_USER_ERROR | E_WARNING | E_USER_WARNING);
$is_user_error = $errno & E_USER_ERROR;
if ($is_user_error) {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
} else {
// do some logging, custom actions, or even ignore the error
}
}
set_error_handler('handle_error');
function test_fake_error() {
trigger_error('Test error', E_USER_ERROR);
}
function test_caught_error() {
try {
test_fake_error();
} catch (Exception $e) {
print "I caught the exception with message: " . $e->getMessage() . "\n";
}
}
function test_uncaught_error() {
test_fake_error();
}
test_Truncated by Planet PHP, read more at the original (another 2548 bytes)