Exceptional Code – PART 1

      1 Comment on Exceptional Code – PART 1

Intended Audience
Introduction
Error Handling Before PHP 5
•  Errors at Script Level
•  Returning Error Flags

Exceptional Code – Part 2


Intended Audience

This article is intended for experienced PHP programmers interested
in learning more about PHP 5’s new Exception support. You
should be comfortable with the basics of object-oriented programming,
including the anatomy of a class and the mechanics of inheritance.

Introduction

Most technical articles skimp on error handling.
This is understandable since clauses that check for error
conditions tend to obscure otherwise good, clean
example code. This article goes to the other extreme. Here you will
encounter plenty of error handling, and very little else.

PHP 5 introduced exceptions, a new mechanism for handling errors in
an object context. As you will see, exceptions provide some significant
advantages over more traditional error management techniques.

Error Handling Before PHP 5

Before the advent of PHP 5 most error handling took place on two
levels. You could:

  • Return an error flag from your method or function, and perhaps set
    a property or global variable that could be checked later on, or
  • Generate a script-level warning or a fatal error using the
    trigger_error() or die() functions.

Errors at Script Level

You can use the die() pseudo-function to end script execution when
there is no sensible way of continuing. You will often see this in
quick and dirty script examples. Here is a simple class that attempts
to load a class file from a directory:


<?php

// PHP 4

require_once('cmd_php4/Command.php');

class CommandManager {

    var
$cmdDir = "cmd_php4";

    function getCommandObject($cmd) {

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

        if (!file_exists($path)) {

            die(
"Cannot find $path\n");

        }

        require_once
$path;

        if (!class_exists($cmd)) {

            die("class $cmd does not exist");

        }

        $ret = new $cmd();

        if (!
is_a($ret, 'Command')) {

            die(
"$cmd is not a Command");

        }

        return $ret;

    }

}

?>

This is a simplified example of what is known as the Command Pattern. The
client coder can save a class to a command directory
(‘cmd_php4’ in this case). As long as the file takes the same name as
the class it contains, and this class is a child of a base class called
Command, our method should generate a usable Command object given a
simple string. The Command base class defines an execute() method, so we
know that anything returned by getCommandObject() will implement

execute().

Let’s look at the Command class, which we store in cmd_php4/Command.php:


<?php

// PHP 4

class Command {

    function
execute() {

        die("Command::execute() is an abstract method");

    }

}

?>




As you can see, Command is a PHP 4 implementation of an abstract class. When
we shift over to PHP 5 later in the chapter, we will implicitly use a cleaner PHP 5
version of this (defined in command/Command.php):


<?php

// PHP 5

abstract class Command {

    abstract function
execute();

}

?>




Here’s a vanilla implementation of a command class. It is called realcommand,
and can also be found in the command directory: cmd_php4/realcommand.php:


<?php

// PHP 4

require_once 'cmd_php4/Command.php';

class
realcommand extends Command {

    function execute() {

        print
"realcommand::execute() executing as ordered sah!\n";

    }

}

?>




A structure like this can make for flexible scripts. You can add
new Command classes at any time, without altering the wider framework.
As you can see though, you have to watch out for a number of potential
show stoppers. We need to ensure that the class file exists where it
should, that the class itself is present, and that it subclasses

Command.

If any of our tests fail, script execution is ended
abruptly. This is safe code, but it’s inflexible. This extreme response
is the only positive action that the method can take. It is responsible
only for finding and instantiating a Command object. It has no
knowledge of any steps the wider script should take to handle a
failure, nor should it. If you give a method too
much knowledge of the context in which it runs it will become hard to
reuse in different scripts and circumstances.

Although using die() circumvents the dangers of embedding script
logic in the getCommandObject() method, it nonetheless imposes a
drastic error response on the script as a whole. Who says that failure
to locate a command should kill the script? Perhaps a default Command

should be used instead, or maybe the command string could be reprocessed.

We could perhaps make things a little more flexible by generating a user warning instead:


<?php

// PHP 4

require_once('cmd_php4/Command.php');

class CommandManager {

    var
$cmdDir = "cmd_php4";

    function getCommandObject($cmd) {

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

        if (!
file_exists($path)) {

            
trigger_error("Cannot find $path", E_USER_ERROR);

        }

        require_once $path;

        if (!class_exists($cmd)) {

            
trigger_error("class $cmd does not exist", E_USER_ERROR);

        }

        $ret = new $cmd();

        if (!
is_a($ret, 'Command')) {

            
trigger_error("$cmd is not a Command", E_USER_ERROR);

        }

        return $ret;

    }

}

?>

If you use the trigger_error() function instead of die() when you
encounter an error, you provide client code with the opportunity
to handle the error. trigger_error() accepts an error message, and a
constant integer, one of:

E_USER_ERROR A fatal error
E_USER_WARNING A non-fatal error
E_USER_NOTICE A report that may not represent an error

You can intercept errors generated using the trigger_error()
function by associating a function with set_error_handler():


<?php

// PHP 4

function cmdErrorHandler($errnum, $errmsg, $file, $lineno) {

    if($errnum == E_USER_ERROR) {

        print
"error: $errmsg\n";

        print
"file: $file\n";

        print
"line: $lineno\n";

        exit();

    }

}

$handler = set_error_handler('cmdErrorHandler');

$mgr = new CommandManager();

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

$cmd->execute();

?>

As you can see, set_error_handler() accepts a function name. If an
error is triggered, the given function is invoked with
four arguments: the error flag, the message, the file, and the line
number at which the error was triggered. You can also set a handler
method by passing an array to set_error_handler(). The first element
should be a reference to the object upon which the handler will be
called, and the second should be the name of the handler method.

Although you can do some useful stuff with handlers, such as
logging error information, outputting debug data and so on, they remain
a pretty crude way of handling errors.

Your options are limited as far as action is concerned. In catching
an E_USER_ERROR error with a handler, for example, you could override
the expected behavior and refuse to kill the process by calling exit()
or die() if you want. If you do this, you must reconcile yourself to
the fact that application flow will resume where it left off. This
could cause some pretty tricky bugs in code that expects an error to
end execution.

Returning Error Flags

Script level errors are crude but useful. Usually, though, more
flexibility is achieved by returning an error flag directly to client
code in response to an error condition. This delegates error handling
to calling code, which is usually better equipped to decide how to
react than the method or function in which the error occurred.

Here we amend the previous example to return an error value on
failure. (false is usually a good choice.)


<?php

// PHP 4

require_once('cmd_php4/Command.php');

class CommandManager {

    var
$cmdDir = "cmd_php4";

    function getCommandObject($cmd) {

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

        if (!file_exists($path)) {

            return
false;

        }

        require_once
$path;

        if (!class_exists($cmd)) {

            return false;

        }

        $ret = new $cmd();

        if (!
is_a($ret, 'Command')) {

            return
false;

        }

        return $ret;

    }

}

?>

This means that you can handle failure in different ways according
to circumstances. The method might result in script failure:


<?php

// PHP 4

$mgr = new CommandManager();

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

if (
is_bool($cmd)) {

    die("error getting command\n");

} else {

    
$cmd->execute();

}

?>



or just a logged error:


<?php

// PHP 4

$mgr = new CommandManager();

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

if(is_bool($cmd)) {

    
error_log("error getting command\n", 0);

    }

else {

    
$cmd->execute();

}

?>

One problem with error flags such as false (or -1, or 0) is that
they are not very informative. You can address this by setting an error
property or variable that can be queried after a failure has been reported:


<?php

// PHP 4

require_once('cmd_php4/Command.php');

class CommandManager {

    var
$cmdDir = "cmd_php4";

    var
$error_str = "";

    function setError($method, $msg) {

        $this->error_str  =

        
get_class($this)."::{$method}(): $msg";

    }

    function error() {

        return
$this->error_str;

    }

    function getCommandObject($cmd) {

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

        if (!
file_exists($path)) {

            $this->setError(__FUNCTION__, "Cannot find $path\n");

            return
false;

        }

        require_once
$path;

        if (!class_exists($cmd)) {

            $this->setError(__FUNCTION__, "class $cmd does not exist");

            return
false;

        }

        $ret = new $cmd();

        if (!is_a($ret, 'Command')) {

            
$this->setError(__FUNCTION__, "$cmd is not a Command");

            return
false;

        }

        return $ret;

    }

}

?>

This simple mechanism allows methods to log error information using
the setError() method. Client code can query this data via the error()

method after an error has been reported. You should extract this
functionality and place it in a base class that all objects in your
scripts extend. If you fail to do this, client code might be forced to
work with classes that implement subtly different error mechanisms. I
have seen projects that contain getErrorStr(), getError(), and error() methods in different classes.

It isn’t always easy to have all classes extend the same base class,
however. What would you do, for example, if you want to extend a third
party class? Of course, you could implement an interface, but if you
are doing that, then you have access to PHP 5, and, as we
shall see, PHP 5 provides a better solution altogether.

You can see another approach to error handling in the PEAR packages.
When an error is encountered PEAR packages return a Pear_Error object
(or a derivative). Client code can then test the returned value with a
static method: PEAR::isError(). If an error has been encountered, then
the returned Pear_Error object provides all the information you might
need including:

PEAR::getMessage() – the error message
PEAR::getType() – the Pear_Error subtype
PEAR::getUserInfo() – additional information about the error or its context
PEAR::getCode() – the error code (if any)

Here we alter the getCommandObject() method so that it returns a

Pear_Error object when things go wrong:


<?php

// PHP 4

require_once("PEAR.php");

require_once('cmd_php4/Command.php');

class CommandManager {

    var $cmdDir = "cmd_php4";

    function getCommandObject($cmd) {

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

        if (!
file_exists($path)) {

            return PEAR::RaiseError("Cannot find $path");

        }

        require_once
$path;

        if (!class_exists($cmd)) {

            return

            PEAR::RaiseError("class $cmd does not exist");

        }

        $ret = new $cmd();

        if (!
is_a($ret, 'Command')) {

            return

            PEAR::RaiseError("$cmd is not a Command");

        }

        return
$ret;

    }

}

?>

Pear_Error is neat for client code because it both signals that an
error has taken place, and contains information about the nature of the error.


<?php

// PHP 4

$mgr = new CommandManager();

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

if (PEAR::isError($cmd)) {

    print
$cmd->getMessage()."\n";

    exit;

}

$cmd->execute();

?>

Although returning an error value allows you to respond to problems
flexibly, it has the side effect of polluting your interface.

PHP does not allow you to dictate the type of value that a method or
function should return, in practice, though it is convenient to be able
to rely upon consistent behavior. The getCommandObject() method returns
either a Command object or a Pear_Error object. If you intend
to work with the method’s return value you will be forced to test its type
every time you call the method. A cautious script can become a tangle
of error check conditionals, as every return type is tested.

Consider this PEAR::DB client code presented without error checking:


<?php

// PHP 4

require_once("DB.php");

$db = "errors.db";

unlink($db);

$dsn = "sqlite://./$db";

$db = DB::connect($dsn);

$create_result = $db->query("CREATE TABLE records(name varchar(255))");

$insert_result = $db->query("INSERT INTO records values('OK Computer')");

$query_result = $db->query("SELECT * FROM records");

$row = $query_result->fetchRow(DB_FETCHMODE_ASSOC);

print $row['name']."\n";

$drop_result = $db->query("drop TABLE records");

$db->disconnect();

?>

The code should be readable at a glance. We open a database, create
a table, insert a row, extract the row, and drop the table. Look what happens
when we code defensively:


<?php

// PHP 4

require_once("DB.php");

$db = "errors.db";

unlink($db);

$dsn = "sqlite://./$db";

$db = DB::connect($dsn);

if (DB::isError($db)) {

    die (
$db->getMessage());

}

$create_result = $db->query("CREATE TABLE records (name varchar(255))");

if (DB::isError($create_result)) {

    die (
$create_result->getMessage());

}

$insert_result = $db->query("INSERT INTO records values('OK Computer')");

if (DB::isError($insert_result)) {

    die (
$insert_result->getMessage());

}

$query_result = $db->query("SELECT * FROM records");

if (DB::isError($query_result)) {

    die (
$query_result->getMessage());

}

$row = $query_result->fetchRow(DB_FETCHMODE_ASSOC);

print $row['name']."\n";

$drop_result = $db->query("drop TABLE records");

if (
DB::isError($drop_result)) {

    die ($drop_result->getMessage());

}

$db->disconnect();

?>

Admittedly, we might be a little less paranoid than this in real-world code, but
this should illustrate the tangle that can result from inline error checking.

So what we need is an error management mechanism that:

  • Allows a method to delegate error handling to client code that is
    better placed to make application decisions
  • Provides detailed information about the problem
  • Lets you handle multiple error conditions in one place, separating
    the flow of your code from failure reports and recovery strategies
  • Does not colonize the return value of a method

PHP 5’s exception handling scores on all these points.

Exceptional Code – Part 2


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.

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