The Standard PHP Library, Part 1

While most of the excitement surrounding the release of PHP 5 focused on its XML and object-oriented features, the Standard PHP Library (SPL) also saw some significant improvements that went mostly unnoticed. In the first of two articles covering the SPL, David Fells discusses the Exception class, which lets programs handle errors more gracefully and simplifies debugging.

Introduction

Amid all the noise and excitement surrounding the release of PHP and its shiny new XML and object oriented features, the Standard PHP Library (SPL) slipped by without much recognition. Exception handling has been a major feature add for PHP 5, and the classes built into PHP are a part of the SPL. This article will demonstrate basic application of several SPL classes and provide information on the classes and interfaces available to PHP 5 programmers.

This article is the first part in a series discussing the classes available in the SPL and their uses. In this article we will discuss the Exception class and its application. Exceptions serve a dual purpose – they allow applications to gracefully handle errors, and they allow simplification of the debugging process, which adds up to a more efficient development process and less stress for developers.

Exceptions

Exceptions have been around for quite a while in languages like C++ and Java, but simple scripting languages have never had support for this graceful error handling device. That changed with the release of PHP 5. Exceptions are not all that special as far as objects go; they are essentially containers for error information. The interesting part is how exceptions are created and identifed. To use exceptions, one only needs to be familiar with the special control structures and functions associated with them. They are built-in and require no configuration and only a handful of brain cells. Exception handling requires proper use of “try”/”catch” blocks and the use of the “throw()” function.

Exceptions are created, or thrown, in one of two ways. Either a developer tells the application to throw an exception when a specific error condition is met (such as data out of the expected range as a function parameter) or the language itself throws an exception when it encounters an application error. The latter type are generated from a lower level in terms of system architecture, but the end result is the same: developers can catch exceptions that are thrown by application code and either ignore them, attempt to recover or gracefully fail. Any option is better than creating a web of error handling methods (such as the PEAR Error class) or by using “die()”, but exceptions are, pun intended, an exceptional solution to the problem of graceful, clean error handling.

{mospagebreak title=Simple Example}

Now that we have covered the basics of what an exception is, we will look at a code snippet to see them in action.

class Math {
  function divide($divisor, $dividend = 1) {
    if (!is_numeric($divisor))
    throw(new Exception(‘Divisor is not a number’));
    if (!is_numeric($dividend))
    throw(new Exception(‘Dividend is not a number’));
    if ($dividend == 0)
    throw(new Exception(‘Division by zero’));
   
    return ($divisor / $dividend);
  }
}

try {
  print Math::divide(1, 0);
}
catch(Exception $e) {
  print $e;
}

If you run this code sample, you will see a dump of the contents of the exception thrown by our divide function. The object contains a lot of useful info like the file name and line where the error occurred and the trace. If you are logging errors or displaying them to the screen (for debugging), this information is priceless. Let’s go over the important parts of this code.

if (!is_numeric($divisor))
  throw(new Exception(‘Divisor is not a number’));
if (!is_numeric($dividend))
  throw(new Exception(‘Dividend is not a number’));
if ($dividend == 0)
  throw(new Exception(‘Division by zero’));

This piece of code checks to see if the divisor and dividend are numbers and if the dividend is a non-zero value. If any of these checks fail, our method calls the new function “throw()”, passing it a new Exception object. We send our message to the constructor of Exception so the debugging information is descriptive enough to be of use and to indicate the specific type of failure encountered by the function. Exceptions can also be extended to give them meaningful names, such as DivisionByZeroException for our case, but in general supplying a suitably descriptive message is adequate.

try {
  print Math::divide(1, 0);
}

Here we attempt to call “divide()”. Placing it in a “try” control structure allows us to logically separate our code blocks, as not all operations require exception handling. We could generate other errors by making calls to divide with non-numeric input.

catch(Exception $e) {
  print $e;
}

This is where the exception is actually caught for handling. We could do something with this error, such as tell the user they cannot divide by zero with the “divide()” function, but in this case we are simply invoking the “$e->__toString()” magic method by treating “$e” as a string – ultimately yeilding an informative error message for the developer.

{mospagebreak title=The Exception Class Hierarchy}

As I noted before, the Exception object is essentially a data container from the developer’s standpoint. The only overridable method is “__toString()”, a magic function invoked by default when the class is used in string context. Below is the class definition for the built-in exception class.

class Exception
{
   protected $message = ‘Unknown exception';  // exception message
   protected $code = 0;             // user defined exception code
   protected $file;                // source filename of exception
   protected $line;                    // source line of exception

   function __construct($message = null, $code = 0);

   final function getMessage();    // message of exception
   final function getCode();          // code of exception
   final function getFile();            // source filename
   final function getTrace();  // an array of the backtrace()
   final function getTraceAsString();   // formated string of trace

   /* Overrideable */
   function __toString();                      // formated string for display
}

The methods provided by the Exception class allow a developer to get all relevant information required to debug an error. The most useful feature is the stack trace, which was previously only available to developers using the Zend Debugger. The stack trace allows you to follow the code path that generated the exception from end to end, tracing back through calling methods, files and lines, until the source function call is displayed.

In complex applications, this information is truly priceless and eliminates, to some degree, the ad hoc debugging methods typically employed in PHP to trace errors such as inserting “die()” or “print_r()” calls at key places in your code to get a trace. You will still have to do these things from time to time but overall, Exceptions should ease debugging pains considerably. Other methods allow you to retreive the message and code that were passed into the constructor, the stack trace in string format (instead of an array), and the file name and line number where the exception was thrown.

Like most other clases, the base class Exception can and should be extended to fit its intended usage. The purpose of extending the Exception class is, almost exclusively, to allow instantiation of Exceptions with intuitive, specific names rather than the nonspecific (but still intuitive) Exception.

Developers should be very careful in how they extend the Exception class, as the purpose of the class itself requires minimal functionality.If a task needs complex work to take place before an Exception can be built with the proper data, then you would be right to put that functionality into the Exception, but adding functionality for functionality’s sake is not the right approach. For example, you would not create several methods to output an error message in various formats. There are already quite a few children of the Exception class in the SPL, all of which are made for use in other SPL classes or by the runtime itself.

{mospagebreak title=A Simple Exception Extension}

The Standard PHP Library

Using our example code above, we can demonstrate a simple extension of the Exception class.

class DivisionByZeroException extends Exception{}

class WrongParameterTypeException extends Exception{}

class Math {
  function divide($divisor, $dividend = 1) {
    if (!is_numeric($divisor))
    throw(new WrongParameterTypeException(‘Divisor is not a number’));
    if (!is_numeric($dividend))
    throw(new WrongParameterTypeException(‘Dividend is not a number’));
    if ($dividend == 0)
    throw(new DivisionByZeroException(‘Division by zero’));
   
    return ($divisor / $dividend);
  }
}

try {
  print divide(1, 0);
}
catch(WrongParameterTypeException $e) {
  print $e;
}
catch(DivisionByZeroException $e) {
  print $e;
}

Using this approach, we are able to easily handle different errors in different ways, and this is where the real value of extending the Exception class comes in. We can chain an unlimited number of catch blocks together to capture the different types of exceptions that could have occurred. We could accomplish a similar feat using error codes (remember the second parameter to the Exception class constructor). To do this we would typically define class constants to identify the error type. We could ammend our code to look like this, for example.

Class Math {
  const DIVISION_BY_ZERO = 1;
  const WRONG_PARAMETER_TYPE = 2;
 
  public static function divide($divisor, $dividend = 1) {
    if (!is_numeric($divisor))
    throw(new Exception(‘Divisor is not a number’, self::WRONG_PARAMETER_TYPE));
    if (!is_numeric($dividend))
    throw(new Exception(‘Dividend is not a number’, self::WRONG_PARAMETER_TYPE)); 
    if ($dividend == 0)
    throw(new Exception(‘Division by zero’, self::DIVISION_BY_ZERO));
   
    return ($divisor / $dividend);
  }
}

try {
  print Math::divide(1, 0);
}
catch(Exception $e) {
  if ($e->getCode() == Math::WRONG_PARAMETER_TYPE))
    die(‘You passed divide() a non-numeric value’);
  if ($e->getCode() == Math::DIVISION_BY_ZERO))
    die(‘You tried to divide by zero’);
  print $e;
}

The same end result is achieved with a different approach. Ultimately this is a matter of taste, but in my experience I have found it easier to keep up with exception classes. Regardless of your individual taste in approaches, this method of error handling is far more elegant and intuitive than anything available in PHP up until now, so take advantage of it.

Conclusion

Exceptions serve two main purposes: to simplify debugging and to allow for graceful error handling. They alllow a clean structure for identifying and creating errors at the script level, and allow simplified error recovery. The PHP 5 implementation of exceptions is, for all intents and purposes, identical to exception handling in Java, .NET and other languages capable of exception handling. Proper use of exceptions in PHP applications should help shed blobs of ugly legacy error handling and clean up code in general considerably. It allows the elimination of a lot of bulky error checks and the consolidation of error handling and recovery routines. Exceptions should be practiced religiously in PHP 5 applications.

Our next installment will discuss another important chunk of the SPL: Iterators. See you soon!

[gp-comments width="770" linklove="off" ]

chat