Zend PHP 5 Certification Study Guide (2014)

Errors and Exceptions

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 Errors and Error Management

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.

Types of PHP Errors

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.

Error Reporting

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.

Handling Errors

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 the error_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 to set_error_handler() in order to return to the default PHP behavior.

Exceptions

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:

·        Exceptions are objects, created (or “thrown”) when an error occurs

·        Exceptions can be handled at different points in a script’s execution, and different types of exceptions can be handled by separate portions of a script’s code

·        All unhandled exceptions are fatal

·        Exceptions can be thrown from the __construct method on failure

·        Exceptions change the flow of the application

The Basic Exception Class

As 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.

Throwing Exceptions

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 the throw expression. You may instantiate the exception object at any time and assign it to a variable to throw 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(\myCode\Exception $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 \myCode\Exception 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 (\myCode\Exception $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 to set_exception_handler() to return to the default PHP behavior.

Finally

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

}

Summary

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.