[ Team LiB ] Previous Section Next Section

Managing Error Conditions with Exceptions

Although it was possible to set a custom error handler in PHP 4, most scripts prior to PHP 5 made do with relatively crude error handling. Methods that ran into trouble would generally choose between using die() to end script execution completely or returning a flag value such as false or -1.

PHP 5 introduces exceptions, allowing methods to hand responsibility back to client code when particular errors are encountered.

An exception is an object that can be automatically populated with information about the error that has occurred and its script context. You must instantiate Exception objects yourself with the new keyword just as you would with any object. Once you have an Exception object, you can literally throw it back to the client code:


function doThing() {
  // uh oh. Trouble!
  throw new Exception( "A generic error", 666 );

  print "this will never be executed";
}

The Exception class's constructor optionally accepts an error string and an error code. When the Exception is thrown with the throw keyword, method execution ends abruptly. Responsibility for handling the problem is passed back to the calling code.

Suppose we were to call the doThing() method as normal:


$test = new ThingDoer();
$test->doThing();

We run into the following error:


Fatal error: Uncaught exception 'exception'! in Unknown on line 0

When you throw an exception in an invoked method, you must make sure that you catch it in the calling code. To do so, you need at least two clauses, try and catch. Within the try block, you attempt to execute the code that might generate an error. Within the catch block, you handle the error condition should it arise:


 try {
  $test = new ThingDoer();
  $test->doThing();
} catch ( Exception $e ) {
  print $e->getMessage();
}

You must declare an argument in the catch clause, as you would in a method declaration. The $e argument in the preceding fragment is automatically populated with an Exception object that you can work with. Notice that we use a hint to make the type of object that we are expecting explicit. The reason this step is necessary will become clear in a little while.

An Exception object has the following methods:


function exception( $message, $errorcode );
function getmessage();
function getcode();
function getfile();
function getline();

You can use them to construct error messages.

If you fail to catch an exception from within a method, then that method will implicitly throw the uncaught exception. It is then up to the method that invoked the current one to catch the exception, and so on. Your script fails if the exception is not handled at some point up the chain.

Defining Custom Exception Classes

Although the default Exception object is useful, you can make it more so by subclassing it and adding your own enhancements. The value can be as much in the names of your subclasses as in any additional methods you define.

Let's extend Exception to report on all its fields:


class MyException extends Exception {
  public function summarize() {
    $ret = "<pre>\n";
    $ret .=  "msg: ".$this->getMessage()."\n"
         ."code: ".$this->getCode()."\n"
         ."line: ".$this->getLine()."\n"
         ."file: ".$this->getFile()."\n";
    $ret .= "</pre>\n";
    return $ret;
  }
}

In this fragment, we define a class called MyException that extends Exception. We create a new method called summarize() which collates the output of all the Exception object's reporting methods.

In Listing 17.6, we take exception handling a stage further by using two additional custom Exception classes. Notice that we gain value from them without adding any further functionality at all.

Listing 17.6 Using Custom Exceptions to Handle Different Circumstances
 1: <?php
 2:
 3: class MyException extends Exception {
 4:   public function summarize() {
 5:     $ret = "<pre>\n";
 6:     $ret .=  "msg: ".$this->getMessage()."\n"
 7:          ."code: ".$this->getCode()."\n"
 8:          ."line: ".$this->getLine()."\n"
 9:          ."file: ".$this->getFile()."\n";
10:     $ret .= "</pre>\n";
11:     return $ret;
12:   }
13: }
14:
15: class FileNotFoundException extends MyException { }
16:
17: class FileOpenException extends MyException { }
18:
19: class Reader {
20:   function getContents( $file ) {
21:     if ( ! file_exists( $file ) ) {
22:       throw new FileNotFoundException( "could not find '$file'" );
23:     }
24:     $fp = @fopen( $file, 'r' );
25:     if ( ! $fp ) {
26:       throw new FileOpenException( "unable to open '$file'" );
27:     }
28:     while ( ! feof( $fp ) ) {
29:       $ret .= fgets( $fp, 1024 );
30:     }
31:     fclose( $fp );
32:     return $ret;
33:   }
34: }
35:
36: $reader = new Reader();
37: try {
38:   print $reader->getContents( "blah.txt" );
39: } catch ( FileNotFoundException $e ) {
40:   print $e->summarize();
41: } catch ( FileOpenException $e ) {
42:   print $e->summarize();
43: } catch ( Exception $e ) {
44:   die("unknown error");
45: }
46: ?>

We define the MyException class on line 3 and then extend it on line 15 and line 17, creating the empty FileNotFoundException and FileOpenException classes. On line 19, we define a class called Reader, which will use our Exception classes. Its getContents() method is designed to read and return the contents of a text file. It requires the path to a file as its sole argument. If a file cannot be found in that path, we throw a FileNotFoundException object on line 22. We then attempt to open the file. If we are unable to acquire a file resource, we throw a FileOpenException on line 26. Assuming we pass these hurdles, we go on to read the file and return its contents.

We try to work with the Reader class on line 36 and onward. Because we know getContents() is liable to throw Exception objects, we wrap our call to the method in a try clause. On line 39, we catch a FileNotFoundException, printing the return value of the summarize() method we defined in the MyException class to the browser. On line 41, we catch the FileOpenException. This is where the empty custom Exceptions come into their own. We are able to provide different behaviors according the type of Exception thrown. If the getContents() method throws a FileNotFoundException, then the relevant catch clause is invoked. We might give a user the chance to re-enter some data in this clause but give up altogether in confusion if the FileOpenException catch clause is invoked. On line 43, we catch a plain Exception object. This line is our backstop; we will deal with any exceptions we have not planned for here. There should be no unexpected Exception objects in our example, but it is often a good idea to keep a backstop in place anyhow, in case new exceptions are added during development.

Exceptions are a great way of keeping your methods clear. You can return the data type your client code expects without confusing matters by returning error flags when things go wrong. Your method can focus on the task at hand, ignoring special cases without damaging the design of your script. You also benefit by forcing client code to take responsibility for error conditions, making for less buggy code. Finally, using multiple catch clauses, you can build up sophisticated responses to errors.

    [ Team LiB ] Previous Section Next Section