Exceptional Code – PART 2

July 29, 2004

Tutorials

Exceptional Code – PART 1

Exceptions in PHP 5
•  Using the throw Keyword
•  The try-catch Statement
•  Handling Multiple Errors
•  Subclassing Exception
•  Passing the Buck

•  More Information about an Exception
Summary
About the Author



Exceptions in PHP 5

We have now discussed error handling in some detail. Although we have yet to encounter
our first exception (!), the ground we have covered should go some way to illustrating
the needs that exceptions meet.

The built-in Exception class includes the following methods:

__construct() The constructor. Requires a message string and an optional integer flag.
getMessage() The error message (as passed to the constructor)
getCode() The error code (as passed to the constructor)
getFile() Returns the path for the file in which the Exception was generated.
getLine() Returns the line number at which the Exception was generated
getTrace() An array that provides information about each step in the progress of an Exception
getTraceAsString() As getTrace() but in string format

As you can see, the Exception class is similar in structure to
Pear_Error. When you encounter an error in your script you can create
your own Exception object:


$ex = new Exception( "Could not open $this->file" );

The Exception class constructor optionally accepts an error
message and an integer error code.


Using the throw Keyword

Having created an Exception object you could then return it as you might a
Pear_Error object, but you shouldn’t! Use the throw keyword instead.
throw is used with an Exception object:


throw new Exception( "my message", 44 );

throw ends method execution abruptly, and makes the associated
Exception object available to the client context. Here’s our
getCommandObject() method amended to use exceptions:


<?php

// PHP 5

require_once('command/Command.php');

class CommandManager {

    private
$cmdDir = "command";

    function getCommandObject($cmd) {

        
$path = "{$this->cmdDir}/{$cmd}.php";

        if (!
file_exists($path)) {

            throw new
Exception("Cannot find $path");

        }

        require_once $path;

        if (!
class_exists($cmd)) {

            throw new
Exception(

                
"class $cmd does not exist");

        }

        $class = new ReflectionClass($cmd);

        if (!$class->isSubclassOf(new ReflectionClass('Command'))) {

            throw new
Exception("$cmd is not a Command");

        }

        return new
$cmd();

    }

}

?>



We use ReflectionClass from the Reflection API to check that the
given class name belongs to the Command type. Running this code with an
invalid file path will result in an error like this:


Fatal error: Uncaught exception 'Exception' with message 'Cannot find command/xrealcommand.php' in /home/xyz/BasicException.php:10

Stack trace:

#0 /home/xyz/BasicException.php(26):

CommandManager->getCommandObject('xrealcommand')

#1 {main}

  thrown in /home/xyz/BasicException.php on line 10

As you can see, throwing an Exception results in a fatal error by default.
This means that code that uses exceptions has safety built-in. An error flag, on
the other hand, does not provide any default behavior. Failure to handle
an error flag simply allows your script to continue execution using an inappropriate value.


The try-catch Statement

In order to handle an Exception at the client end, we must use a
try-catch statement. This consists of a try clause and at least one
catch clause. Any code that invokes a method that might throw an

Exception should be wrapped in the try clause. The
catch clause is used to handle the Exception should it be
thrown. Here’s how we might handle an error thrown from getCommandObject():

<?php

// PHP 5

try {

    
$mgr = new CommandManager();

    
$cmd = $mgr->getCommandObject('nrealcommand');

    
$cmd->execute();

} catch (Exception $e) {

    print
$e->getMessage();

    exit();

}

?>

As you can see, the Exception object is made available to the catch

clause via an argument list similar to the kind you might find in a
method or function declaration. We can query the provided Exception
object in order to get more information about the error. By using the
throw keyword in conjunction with the try-catch statement we avoid
polluting our method’s return value with an error flag.

If an Exception is thrown, execution within the try clause will
cease abruptly, and flow will switch immediately to the catch clause.

As we have seen, if an Exception is left uncaught, a fatal error results.


Handling Multiple Errors

Exception handling so far has not been so different from code that
checks return values for error flags or objects. Let’s make the
CommandManager class a bit more cautious, and have it check the command
directory in the constructor:

<?php

// PHP 5

require_once('command/Command.php');

class CommandManager {

    private
$cmdDir = "command";

    function __construct() {

        if (!is_dir($this->cmdDir)) {

            throw new
Exception(

            
"directory error: $this->cmdDir");

        }

    }

    function getCommandObject($cmd) {

        $path = "{$this->cmdDir}/{$cmd}.php";

        if (!
file_exists($path)) {

            throw new
Exception("Cannot find $path");

        }

        require_once
$path;

        if (!class_exists($cmd)) {

            throw new
Exception("class $cmd does not exist");

        }

        $class = new ReflectionClass($cmd);

        if (!$class->isSubclassOf(new ReflectionClass('Command'))) {

            throw new
Exception("$cmd is not a Command");

        }

        return new
$cmd();

    }

}

?>

There are now two invocations that might cause an error. However,
we don’t need to change our client code at all. You can include as many statements as
you want in a try clause, and still handle all errors in one place. If
the CommandManager object’s constructor throws an exception then
execution in the try clause stops, and the catch clause is
invoked with the relevant Exception object. The same is true of the

getCommandObject() invocation. So we have two potential causes of error
in one place, and a single clause for managing any problems in another. This allows you
to write cleaner code without sacrificing error handling.


<?php

// PHP 5

try {

    
$mgr = new CommandManager(); // potential error

    
$cmd = $mgr->getCommandObject('realcommand');

                                // another potential error

    
$cmd->execute();

} catch (
Exception $e) {

    
// handle either error here

    
print $e->getMessage();

    exit();

}

?>


There is one problem that we haven’t yet addressed. How do we distinguish between
different types of error? For example, we may wish to deal with a missing directory
in one way, and an illegal command class in another.

The Exception class accepts an optional integer error flag, which is
one way of distinguishing between error types in your catch clause.

<?php

// PHP 5

require_once('command/Command.php');

class CommandManager {

    private
$cmdDir = "command";

    const
CMDMAN_GENERAL_ERROR = 1;

    const CMDMAN_ILLEGALCLASS_ERROR = 2;

    function __construct() {

        if (!
is_dir($this->cmdDir)) {

            throw new
Exception("directory error: $this->cmdDir", self::CMDMAN_GENERAL_ERROR);

        }

    }

    function getCommandObject($cmd) {

        
$path = "{$this->cmdDir}/{$cmd}.php";

        if (!
file_exists($path)) {

            throw new Exception("Cannot find $path", self::CMDMAN_ILLEGALCLASS_ERROR);

        }

        require_once
$path;

        if (!
class_exists($cmd)) {

            throw new Exception("class $cmd does not exist", self::CMDMAN_ILLEGALCLASS_ERROR);

        }

        $class = new ReflectionClass($cmd);

        if (!$class->isSubclassOf(new ReflectionClass('Command'))) {

            throw new
Exception("$cmd is not a Command", self::CMDMAN_ILLEGALCLASS_ERROR);

        }

        return $class->newInstance();

    }

}

?>

By passing one of CMDMAN_ILLEGALCLASS_ERROR or CMDMAN_GENERAL_ERROR

to any Exception object that we throw, we make it possible
for client code to recognize different error categories, and define distinct
strategies for handling the problem.


<?php

// PHP 5

try {

    
$mgr = new CommandManager();

    
$cmd = $mgr->getCommandObject('realcommand');

    $cmd->execute();

} catch (
Exception $e) {

    if (
$e->getCode() == CommandManager::CMDMAN_GENERAL_ERROR) {

        
// no way of recovering

        die($e->getMessage());

    } else if (
$e->getCode() == CommandManager::CMDMAN_ILLEGALCLASS_ERROR) {

        
error_log($e->getMessage());

        print "attempting recovery\n";

        
// perhaps attempt to invoke a default command?

    
}

}

?>

We can achieve a similar effect by throwing and catching distinct
Exception subclasses.


Subclassing Exception

There are two clear reasons why you might want to subclass Exception. These are:

  • To provide specialized functionality within your subclass
  • To distinguish one error type from another as a service to client code.

Let’s look at the second instance. We might work with two kinds of
error in the CommandManager class: a general error category (covering a
missing command directory, for example), and a set of errors associated
with the failure to locate or generate Command objects.

We might define two Exception subtypes for these cases:


<?php

// PHP 5

require_once('command/Command.php');

class CommandManagerException extends Exception{}

class IllegalCommandException extends Exception{}

class CommandManager {

    private
$cmdDir = "command";

    function __construct() {

        if (!is_dir($this->cmdDir)) {

            throw new
CommandManagerException("directory error: $this->cmdDir");

        }

    }

    function getCommandObject($cmd) {

        $path = "{$this->cmdDir}/{$cmd}.php";

        if (!
file_exists($path)) {

            throw new
IllegalCommandException("Cannot find $path");

        }

        require_once
$path;

        if (!class_exists($cmd)) {

            throw new
IllegalCommandException("class $cmd does not exist");

        }

        $class = new ReflectionClass($cmd);

        if (!$class->isSubclassOf(new ReflectionClass('Command'))) {

            throw new
IllegalCommandException("$cmd is not a Command");

        }

        return
$class->newInstance();

    }

}

?>

When our class fails to find a command directory it throws a
CommandManagerException. When it encounters difficulties in generating
a Command object, the getCommandObject() method throws an

IllegalCommandException. Note that there are a number of different
reasons why an IllegalCommandException might be thrown. We could
combine the two previous examples here and provide an error code
constant in the IllegalCommandException for each potential cause.

Now that the CommandManager class can fail in these novel ways, we
can add new catch clauses to match the different error types:


<?php

// PHP 5

try {

    
$mgr = new CommandManager();

    
$cmd = $mgr->getCommandObject('realcommand');

    $cmd->execute();

} catch (
CommandManagerException $e) {

    die(
$e->getMessage());

} catch (
IllegalCommandException $e) {

    
error_log($e->getMessage());

    print "attempting recovery\n";

    
// perhaps attempt to invoke a default command?

} catch (Exception $e) {

    print
"Unexpected exception\n";

    die(
$e->getMessage());

}

?>


If the CommandManager object above throws a CommandManagerException,
then the corresponding catch is executed. This is not a given, however.
The argument portion of each catch clause acts like a test. The first
match is the one executed. For this reason, you should always organize
your catch clauses from the most specific to the most general. If we
were to reorganize our catch clauses like this:


<?php

// PHP 5

try {

    
$mgr = new CommandManager();

    
$cmd = $mgr->getCommandObject('realcommand');

    $cmd->execute();

} catch (
Exception $e) {

    print
"Unexpected exception\n";

    die(
$e->getMessage());

} catch (
CommandManagerException $e) {

    die($e->getMessage());

} catch (
IllegalCommandException $e) {

    
error_log($e->getMessage());

    print
"attempting recovery\n";

    // perhaps attempt to invoke a default command?

}

?>

the first clause would always be executed when an exception
was thrown. This is because every exception belongs to the Exception
type and the first clause will therefore always make a match.

If you are catching specific Exception subtypes in your catch
clauses, it is a good idea to implement a final clause that catches the
Exception type. This then acts as a catch-all. Of course, you may wish
to pass the buck along to the method that called the client code. This
is another feature of PHP’s exception functionality that should be discussed.


Passing the Buck

We have already established that some errors cannot be handled at
the point at which they occur. A good solution to this
can’t-see-the-wood-for-the-trees scenario is to pass responsibility
back up to the code that called the current method. What happens,
though, if the calling code does not itself have the perspective to
deal with the problem? Well, we can always re-throw the error. Let’s
expand the client code we have been working with so that it forms part
of a simple class:


<?php

// PHP 5

class RequestHelper {

    private
$request = array();

    private
$defaultcmd = 'defaultcmd';

    private
$cmdstr;

    function __construct($request_array=null) {

        if (!
is_array($this->request = $request_array)) {

            $this->request=$_REQUEST;

        }

    }

    function getCommandString() {

        return (
$this->cmdstr ? $this->cmdstr : ($this->cmdstr=$this->request['cmd']));

    }

    function runCommand() {

        
$cmdstr = $this->getCommandString();

        try {

            
$mgr = new CommandManager();

            
$cmd = $mgr->getCommandObject($cmdstr);

            $cmd->execute();

        } catch (
IllegalCommandException $e) {

            
error_log($e->getMessage());

            if (
$cmdstr != $this->defaultcmd) {

                $this->cmdstr = $this->defaultcmd;

                
$this->runCommand();

            } else {

                throw
$e;

            }

        } catch (Exception $e) {

            throw
$e;

        }

    }

}

$helper = new RequestHelper(array(cmd=>'realcommand'));

$helper->runCommand();

?>

We have wrapped our client code in a class called RequestHelper. The
RequestHelper class is responsible for managing user-provided data. In
the constructor we optionally accept a debug array. If no such array is
forthcoming, the class uses the superglobal $_REQUEST array. Whichever
array is used, it is assigned to a property called $request. Client
code signals the command it wishes to execute by providing a ‘cmd’
element in the request array. The getCommandString() method tests a
property called $cmdstr. If it is empty (which it is, to start
with) then the method assigns the contents of the $request property’s
‘cmd’ element to $cmdstr, returning the result. If it is not empty,
the method simply returns the $cmdstr property. Through this mechanism,
the command string can be overridden within the RequestHelper class.

RequestHelper has a relatively narrow remit, so for all exceptions
but IllegalCommandException objects we defer any error handling to a
higher level class. We do this in the final catch clause by manually
throwing the caught Exception class:

} catch (Exception $e) {

    throw
$e;

}

If we catch an IllegalCommandException, however, we first attempt to
invoke a default command. We do this by setting the $cmdstr property to
the same value as $defaultcmd and then invoking the runCommand method
recursively. If the $cmdstr and $defaultcmd strings are already
equivalent, there is no further action we can take, and we re-throw the exception.

In fact Zend Engine 2 will automatically re-throw any exceptions that you don’t
catch yourself, so we could omit the final catch clause
altogether with no change in script functionality. (We have already taken
advantage of this feature in our example.) Here is the final line of the
CommandManager::getCommandObject() method:


return $class->newInstance();


We beg a couple of questions here.

Firstly, we have assumed that the constructor requires no arguments. In this article we
will not deal with cases where this is not so.

Secondly, we have assumed that the command
can be instantiated. If the constructor was declared private, for example,
this statement would throw a ReflectionException object. If we
don’t handle this in RequestHelper, then the exception will be passed up to
the code that invoked RequestHelper, and so on. Where an exception may
be thrown implicitly it is a good idea to signal as much in your documentation, or
even to manually throw the exception so that other programmers are
forewarned to handle this eventuality.


More Information about an Exception

Here is some code that formats Exception information.


<?php

// PHP 5

class Front {

    static function
main() {

        try {

            $helper = new RequestHelper(array(cmd=>'realcommand'));

            
$helper->runCommand();

        } catch (
Exception $e) {

            print
"<h1>".get_class($e)."</h1>\n";

            print "<h2>{$e->getMessage()}

                ({$e->getCode()})</h2>\n\n"
;

            print
"file: {$e->getFile()}<br />\n";

            print
"line: {$e->getLine()}<br />\n";

            print $e->getTraceAsString();

            die;

        }

    }

}

Front::main();

?>

If you make the realcommand class impossible to instantiate (by
declaring its constructor private) and run this code, you will see this
output:


<h1>ReflectionException</h1>

<h2>Access to non-public constructor of class realcommand (0)</h2>

file: /home/xyz/TraceStackException.php<br />

line: 30<br />

#0 /home/xyz/TraceStackException.php(53): CommandManager->getCommandObject()

#1 /home/xyz/TraceStackException.php(73): RequestHelper->runCommand('realcommand')

#2 /home/xyz/TraceStackException.php(85): Front::main()

#3 {main}

As you can see, getFile() and getLine() do just what you
would expect: they return the file name and line number for the exception. The getStackAsString() method returns details about
each layer of invocation that led to the exception’s generation.

You can also get at this information in array format using the
getTrace() method. getTrace() returns a multi-dimensional
array. As you’d expect, the first element contains information about the location
at which the exception is generated, the next element details the outer
method call, and so on until the topmost level is reached. Each element
in this array is itself an array with the following fields:

file The file in which the method invocation was made
line The line number of the offending method call
function The name of the offending method
class The class on which the invocation was made
type The kind of call, ‘::’ for static, or ‘->’ for instance invocations
args The arguments for the method call


Summary

Exceptions provide some key benefits.

By grouping error handling code in catch statements you can
separate your error handling from your application flow. This can make your
code easier to read, and your coding practice more pleasant. I often begin
with a strict policy of catching all exceptions in a method and killing script
execution. I then introduce additional flexibility as needed. This draconian policy
gets me up and running with safe and easy exception management straight away.

Exceptions flow from the low level to the high. That is,
exceptions are passed back to the code that is best placed to decide
how to handle an error. At first it may seem to defy common sense, but
it is usually the case that the point at which an error occurs is the
worst place to decide what to do about the error.

The throw/catch mechanism provided by exceptions sidesteps the
problems associated with returning error values from methods. The
return value of a method can be determined entirely by the logic of
your class. Client code can expect a particular return type, without
the wearisome task of continual testing.


About the Author

Matt Zandstra is a writer and consultant specializing in server
programming and training. With his business partner, Max Guglielmino,
he runs Corrosive, a
technical agency that provides open source/open standards training and plans,
designs and builds Internet applications.

Exceptional Code – PART 1


Matt is the author of SAMS Teach Yourself PHP in 24 Hours. He is currently
working on a book about object-oriented PHP.

About Cal Evans

Many moons ago, at the tender age of 14, Cal touched his first computer. (We're using the term "computer" loosely here, it was a TRS-80 Model 1) Since then his life has never been the same. He graduated from TRS-80s to Commodores and eventually to IBM PC's. For the past 10 years Cal has worked with PHP and MySQL on Linux OSX, and when necessary, Windows. He has built on a variety of projects ranging in size from simple web pages to multi-million dollar web applications. When not banging his head on his monitor, attempting a blood sacrifice to get a particular piece of code working, he enjoys building and managing development teams using his widely imitated but never patented management style of "management by wandering around". Cal is currently based in Nashville, TN and is gainfully unemployed as the Chief Marketing Officer of Blue Parabola, LLC. Cal is happily married to wife 1.28, the lovely and talented Kathy. Together they have 2 kids who were both bright enough not to pursue a career in IT. Cal blogs at http://blog.calevans.com and is the founder and host of Day Camp 4 Developers

View all posts by Cal Evans

Comments are closed.