Errors are an integral part of every computer language—although one that, most of the time, programmers would rather not have to deal with!
PHP has two mechanisms for errors, the standard PHP errors, and exceptions. Most newer PHP extensions (e.g. PDO) will throw exceptions, and most userland code has also migrated to exceptions.
PHP has some excellent facilities for dealing with errors that provide an excellent level of fine-grained control over how errors are thrown, handled, and reported. Proper error management is essential to writing applications that are both stable and capable of detecting when the inevitable problem arises, thus handling failure gracefully.
There are several types of errors—usually referred to as error levels in PHP:
Error Level | Constants | Description |
---|---|---|
Compile-time errors | E_PARSE , E_COMPILE_ERROR |
Errors detected by the parser while it is compiling a script. Cannot be trapped from within the script itself. |
Fatal errors | E_ERROR , E_USER_ERROR |
Errors that halt the execution of a script. Cannot be trapped. |
Recoverable errors | E_RECOVERABLE_ERROR |
Errors that represent significant failures, but can still be handled in a safe way. |
Warnings | E_WARNING . E_CORE_WARNING , E_COMPILE_WARNING , E_USER_WARNING |
Recoverable errors that indicate a run-time fault. Do not halt the execution of the script. |
Notices | E_NOTICE , E_USER_NOTICE |
Indicate that an error condition occurred, but is not necessarily significant. Do not halt the execution of the script. |
Informational | E_DEPRECATED , E_USER_DEPRECATED , E_STRICT |
Indicates a condition that you should check in your code, because it may indicate functionality that will change or be removed in a future version |
As you can see, it is not always possible for a script to detect a fault and recover from it. With the exception of parsing errors and fatal errors, however, your script can at least be advised that a fault has occurred, thus giving you the possibility of handling failure gracefully.
By default, PHP reports any errors it encounters to the script’s output. Unless you happen to be in a debugging environment, you will probably not want to take advantage of this feature: allowing users to see the errors that your scripts encounter is not just bad form—it could be a significant security issue.
Luckily, several configuration directives in the php.ini
file allow you to fine-tune how—and which—errors are reported. The most important ones are error_reporting
, display_errors
, and log_errors
.
The error_reporting
directive determines which errors are reported by PHP. A series of built-in constants allow you to prevent PHP from reporting errors beneath a certain pre-defined level. For example, the following allows for the reporting of all errors, except notices:
error_reporting=E_ALL & ~E_NOTICE
Error reporting can also be changed dynamically from within a script by calling the
error_reporting()
function.
The display_errors
and log_errors
directives can be used to determine how errors are reported. If display_errors
is turned on, errors are added to the script’s output; generally speaking, this is not desirable in a production environment, as everyone will be able to see your scripts’ errors. Under those circumstances, you will instead want to turn on log_errors
, which causes errors to be written to your web server’s error log.
You can change the file used for logging with the
error_log
directive, which takes the name of a file. Make sure the file is writeable by your script.
Your scripts should always be able to recover from a trappable error, even if it’s just to advise the user and to notify support staff that an error occurred. This way, your script won’t simply capitulate when something unexpected occurs, resulting in better communication with your users and the possible avoidance of some major problems.
Luckily, error handling is very easy. Your scripts can declare a catch-all function that is called by PHP when an error condition occurs by calling the set_error_handler()
function:
Listing 12.1: Logging all errors
<?php
$oldErrorHandler = '';
function myErrorHandler($no, $str, $file, $line, $context) {
global $oldErrorHandler;
logToFile("Error $str in $file at line $line");
// Call the old error handler
if ($oldErrorHandler) {
$oldErrorHandler(
$no, $str, $file, $line, $context
);
}
}
$oldErrorHandler = set_error_handler('myErrorHandler');
As you can see, the function name of the old error handler (if any) is returned by the call to set_error_handler()
. This allows you to stack several error handlers on top of each other, thus making it possible to have different functions handle different kinds of errors.
It’s important to keep in mind that your error handler will completely bypass PHP’s error mechanism, meaning that you will be responsible for handling all errors, and stopping the script’s execution if necessary.
As of PHP 5.0,
set_error_handler()
supports a second parameter that allows you to specify the types of errors that a particular handler is responsible for trapping. This parameter takes the same constant values as theerror_reporting()
function.
PHP also provides a restore_error_handler()
to revert back to the the previous error handler—which may be the built-in default.
New in PHP 5.5: With PHP 5.5 you can pass
NULL
toset_error_handler()
in order to return to the default PHP behavior.
Even though they have been a staple of object-oriented programming for years, exceptions become part of the PHP arsenal with the relase of PHP 5.0. Exceptions provide an error control mechanism that is more fine-grained than traditional PHP fault handling, and allows for a much greater degree of control.
There are several key differences between “regular” PHP errors and exceptions:
__construct
method on failureAs we mentioned in the previous paragraph, exceptions are objects that must be direct or indirect (for example, through inheritance) instances of the Exception
base class. The latter is built into PHP itself, and is declared as follows:
Listing 12.2: The base Exception class
class Exception
{
// The error message associated with this exception
protected $message = 'Unknown Exception';
// The error code associated with this exception
protected $code = 0;
// The pathname of the file where the exception occurred
protected $file;
// The line of the file where the exception occurred
protected $line;
// Constructor
function __construct ($message = null, $code = 0);
// Returns the message
final function getMessage();
// Returns the error code
final function getCode();
// Returns the file name
final function getFile();
// Returns the file line
final function getLine();
// Returns an execution backtrace as an array
final function getTrace();
// Returns a backtrace as a string
final function getTraceAsString();
// Returns a string representation of the exception
function __toString();
}
Almost all of the properties of an Exception
are automatically filled in for you by the interpreter. Generally speaking, you only need to provide a message and a code, and all the remaining information will be taken care of for you.
Since Exception
is a normal (if built-in) class, you can extend it and effectively create your own exceptions, thus providing your error handlers with any additional information that your application requires.
Exceptions are usually created and thrown when an error occurs by using the throw
construct:
Although it is common practice to do so, you don’t need to create the
Exception
object directly in thethrow
expression. You may instantiate the exception object at any time and assign it to a variable tothrow
later.
if ($error) {
throw new Exception("This is my error");
}
Exceptions then “bubble up” until they are either handled by the script or cause a fatal exception. The handling of exceptions is performed using a try...catch
block:
Listing 12.3: Bubbling exception through try...catch
try {
if ($error) {
throw new Exception("This is my error");
}
} catch (Exception $e) {
// Handle exception
}
In the example above, any exception that is thrown inside the try{}
block is going to be caught and passed on to the code inside the catch{}
block, where it can be handled as you see fit.
Note how the catch()
portion of the statement requires us to hint the type of Exception that we want to catch; one of the best features of exceptions is the fact that you can decide which kind of exception to trap.
Note: You will need to fully qualify your exception hints if you are using namespaces!
Since you are free to extend the base Exception
class, this means that different nested try..catch
blocks can be used to trap and deal with different types of errors:
Listing 12.4: Extending the base Exception class
namespace myCode;
class Exception extends Exception { }
try {
try {
try {
new PDO("mysql:dbname=zce");
throw new myException("An unknown error occurred.");
} catch (PDOException $e) {
echo $e->getMessage();
}
} catch(myCodeException $e) {
echo $e->getMessage();
}
} catch (Exception $e) {
echo $e->getMessage();
}
In this example, we have three nested try... catch
blocks; the innermost one will only catch PDOException
objects, while the next will catch the custom myCodeException
objects, and the outermost will catch any other exceptions that we might have missed. Rather than nesting the try...catch
blocks like we did above, you can also chain just the catch
blocks:
Listing 12.5: Catching different exceptions
try {
new PDO("mysql:dbname=zce");
throw new myException("An unknown error occurred.");
} catch (PDOException $e) {
echo $e->getMessage();
} catch (myCodeException $e) {
echo $e->getMessage();
} catch (Exception $e) {
echo $e->getMessage();
}
Once an exception has been caught, execution of the script will follow from directly after the last catch
block.
To avoid fatal errors from uncaught exceptions, you could wrap your entire application in a try... catch
block, but this would be rather inconvenient. Luckily, there is a better solution: PHP allows us to define a “catch-all” function that is automatically called whenever an exception is not handled. This function is set up by calling set_exception_handler()
:
Listing 12.6: Handling uncaught exceptions
function handleUncaughtException($e) {
echo $e->getMessage();
}
set_exception_handler("handleUncaughtException");
throw new Exception("You caught me!");
echo "This is never displayed";
Note that, because the catch-all exception handler is only called after the exception has bubbled up through the entire script, it, just like an all-encompassing try... catch
block, is the end of the line for your code. In other words, the exception has already caused a fatal error, and you are given the opportunity to handle it, but are not given the opportunity to recover from it. For example, the code above will never output You caught me!
, because the exception thrown will bubble up and cause handleUncaughtException()
to be executed; the script will then terminate.
If you wish to restore the previously used exception handler, be it the default of a fatal error or another user defined callback, you can use
restore_exception_handler()
.New in PHP 5.5: With PHP 5.5 you can pass
NULL
toset_exception_handler()
to return to the default PHP behavior.
With PHP 5.5, a finally
block was added. Code within the finally
block will be executed regardless of whether an exception has been thrown or not, making it useful for things like cleanup.
Listing 12.7: Using a finally
block
try {
// Try something
} catch (Exception $exception) {
// Handle exception
} finally {
// Whatever happened, do this
}
Errors and exceptions are a fact of life, and as every developer knows, an error is much better than the white screen of death! Even though PHP has not embraced exceptions for all internal functionality, exceptions are—especially with the addition of finally
—a great tool to have in your toolbox. Used well, exceptions can lead to better code that is better organized, has more granularity in handling errors, and is easier to maintain. However, we musn’t forget about standard PHP errors: they will remain a part of our workflow for the forseeable future.
3.15.237.31