Categories


Loading feed
Loading feed

Error Handling: Stepping beyond True/False Results


Intended Audience
Introduction
The Limitations of True/False Returns
Keep your Problems to Yourself
Dealing with your Problems
Conclusions
About the author

Intended Audience

This article is intended for the intermediate to advanced PHP programmer. Basic knowledge of creating and using functions and classes is assumed.

Introduction

Proper error handling is a struggle for most software engineers, myself included. There is no fun in error handling, no logic puzzles to solve. Most of us don’t add any kind of error handling to our code until we run into a problem.

When Sun developers designed Java and created their exceptions, they understood the fundamental laziness of programmers. Therefore, in Java, you must handle a method that might cause an exception; if you don't, your code will not compile. PHP offers no such handholding, and therefore it is up to you, the programmer to take preemptive action.

This article will give you the capabilities to handle errors in an elegant manner without diverging too much from your lazy ways. We will look at how to handle PHP script errors, as well as passing around your own errors with the PEAR module: PEAR_Error.

The Limitations of True/False Returns

For most of us, error handling in a function is straightforward. If an error occurs, return 0, otherwise, things are looking good so return 1. This is the standard true or false result that is the norm for new and experienced PHP programmers alike.

This time honored approach has many flaws, however. First, you might actually want to return some piece of data that is useful to the calling code. Say you have the method getAccountBalance(). Now in my case (what with the US economy the way it is), this method is going to return 0. Now, if you're expecting to return zero in case of an error, how do you differentiate between an error and a zero balance? Supposing you plan to do so by returning 99999999999 to mean false, thinking that no one will ever have a balance that high. Building code based on lazy assumptions like that can be risky, just ask any Y2K programmer.

There's more than simple account balance assumptions to this argument, however, and I've got more convincing to do. Suppose that the calling code would like to know why its function call failed. Your good old friends 1 and 0 don’t have much to say about that.

It turns out that when it comes to the results of a function call, there is true, and there is false, and then there is, well something else. For example, if I call addPersonToFamily(“Bob”, “Johnson”) and Bob happens to already be a member of the Johnson family, is this a failure? Not necessarily. I would argue it's that third element: the thing that’s neither true nor false. No error occurred, and Bob is in the family Johnson, just like the caller asked. In a case like this it would be nice to let the caller know why the call failed.

If you're still not ready to dump your old true/false ways, then I have only two words for you: I’m right (ok, it's one word and one contraction). The true/false method of error handling is uninformative, inelegant, and should become a thing of the past.

Keep your Problems to Yourself

There are many different kinds of error that can occur when writing PHP code, such as parse errors, warnings, and even internal PHP errors. We’ve all gone to a PHP page and seen the dreaded message, Warning: You screwed something up, or something like that. You never want your end user to see anything like this. Even if it's not you but some other sloppy programmer on your team who creates code that causes an error, you want the user to see a nice, happy page telling him not to worry. Thankfully the wise developers of PHP thought the same way and built in the just facilities you need to accomplish that.

You can tell PHP to override the default error handler (the one that prints out that ugly code onscreen) with your own, friendlier version. To do this, call the PHP function set_error_handler (callback error_handler) with the method or function name you would like to use. I will briefly explain the use of this method, but you may want to refer to the complete documentation in the PHP manual.

Our new handler method will need to accept two parameters, and can accept up to five. The first two required parameters are the PHP error number, and the actual error message. The next is the name of the file, the line number, and the context in which the error occurred. We’ll delve into the context a bit a little later, but be sure to check the docs for more info on that.

Take a look at my custom error handler below and then we’ll discuss it:

/* use custom error handling /
set_error_handler("my_error_handler");
/ start buffering the page output */
ob_start();
function 
my_error_handler($errno$errstr$errfile$errline
{
        
/* put all the information we have into a line which
     * will get logged */
$errLogLine date("n/j/y H:i:s")." - ".
$errfile.":".$errline.$contextENV.": ".
 
$errstr."\n";

        
/* log it to our log file */
        
error_log ($errLogLine3"/tmp/my-errors.log");

        
/* exit /
        header ("Location: HappyPage.php");
        / clean up the buffer /
        ob_end_clean();
        / stop further processing of the page */
        
exit;
        
}

/* page content goes here */

ob_end_flush();

The first step is to tell PHP you will be handling errors yourself; that's what set_error_handler does. Next, you want to make sure that nothing is sent to the user until you have ensured that it is error free; so you turn on output buffering. You now declare the handler and the parameters you will be expecting. This handler performs two main tasks: first it to logs the offending error to a file, and second it to redirects the user to a happy page informing him that an error has occurred but not to worry.

It is important to note that there are certain low-level warnings that PHP may complain about but that normally shouldn't cause you to stop processing. For example, if you reference a hash without quotes around the key as in, $foo[bar]="Value In Here" you will receive a notice, but your code will probably still work fine. To make sure your error handler does not stop execution in this case you can simply put the following if statement around the header and exit code:

    if ($errno != E_NOTICE){
    
/* header & exit code here */
    
}

This code will not exit or redirect for notices. Check the Error Handling and Logging Functions documentation for a list of all error levels.

The best way I’ve found to ensure that your error handler is always in place is to stick it in a file and then either include it manually at the top of every page or use the auto_prepend_file PHP configuration directive to automatically ensure your handler is always in place. You will also want to create a similar footer which includes ob_end_flush().

Dealing with your Problems

I’ve covered how to properly handle errors reported by PHP, now I will discuss handling errors that occur within functions you write. As I hope I convinced you before, returning simply true or false from your functions is a poor excuse for error handling. But I can't make such an assertion without an alternative, right? So, here goes.

My preferred method is to use error objects. An error object is an instance of an error class that contains information about the error that has occurred. An error class simply includes various data members that describe the error, like an error code, an error message, or information about where the error occurred.

Here's how it works: If your function returns a Boolean, then return 1 for a success, and an error object for a failure. If your function returns data, then return the data for success and an error object for failure.

It would be fairly simple to write your own error class, but it's not necessary, someone has done it for you. If you are using a modern version of PHP you should have the wonderful PEAR system installed. One of PEAR's core modules is PEAR_Error, an error class that does just what I’ve described above . Full documentation on this is covered in the PEAR manual under PEAR_Error as well as under the PEAR base class.

Simple example

You can use the error class in a variety of ways, ranging from very easy to somewhat complex. On the easy end, you can begin using the PEAR_Error class with as few as two lines of code. In the first line, you return the error like this:

return new PEAR_Error("Error String")

The second line is from the calling code and checks to see if an error occurred:

if (PEAR::isError($result))

I know I said you only needed two lines but you will also need to include the PEAR base class, using something like:

require_once "PEAR.php".

More complex example

While that's the easy way, however, my use of the PEAR_Error class is a bit more complex. Let’s take a look:

class ATM  {
        
function initATM($balance)
{    
            
/* setup account information */
            
$this->balance $balance;
            if (!
is_numeric($balance)) {
                unset(
$this->balance);
            
$localInfo =
array (
"class" => get_class($this),
"method" => "initATM",
"line" => __LINE__
"file" => __FILE__
);

            return new 
PEAR_Error("Balance must be numeric",
101PEAR_ERROR_TRIGGERE_USER_NOTICE,
$localInfo);  
        }

 
}/* end ATM class    */

Let’s quickly review what’s going on here. This is a simple class that emulates an ATM. The method shown, initATM, is the parameter balance and must be numeric. I check to make sure it is numeric and if it isn’t, I return an error. (You’ll notice that I’m not using a constructor because values cannot be returned from constructors.)

This error returns all possible parameters that the PEAR_Error class can handle. The first, as we saw before is the error message; next is an error code of my choosing. After that it gets a bit tricky.

The third parameter specifies the mode of operation we are using, which is PEAR_ERROR_TRIGGER. This means that PHP’s trigger_error() function will be called when this object is created. (For a complete list of error modes check the PEAR_ERROR documentation).

The next parameter specifies that when trigger_error() is called, the error type should be an E_USER_NOTICE. And finally we have the locationInfo hash. This allows us to pass along extra information about the error as part of the error object. I’ve placed the class name, the method name, the line number and the file name in the locationInfo hash and passed it with the new error object.

To deal with a returned PEAR_Error object, there are several get functions. They aren’t well documented in the PEAR documentation so I’ve listed commonly used ones here as found in the PEAR.php code:
  • getMode() - Get the error mode from an error object
  • getCallback()- Get the callback function/method from an error object
  • getMessage()- Get the error message from an error object
  • getCode()- Get error code from an error object
  • getUserInfo()- Get additional user-supplied information
Here is how I might call my ATM class and deal with errors:

$teller = new ATM();
$initResult $teller->initATM("a123.33"123341);
if (
PEAR::isError($initResult)) {
    print 
"<font color='red'>Object teller error: ".$initResult->getMessage()."</font><br />\n";
} else {
    print 
"<font color='green'>Object teller created & initialized</font><br />\n";
}

This demonstrates how I check for an error, and then display the error message. Now you will not want to blindly display all error messages to the end user. You can use the error code to decide when to display the message from the error object to the user and when to simply say, “Sorry, an error has occurred.” Assign all errors that should not be displayed to the user an error code of 1-100; and assign all errors that can be displayed to the user a 101-200 code. Then, when an error occurs the page logic can check the error code using the getCode() function and decide what to display.

In addition to handling errors at the display level, your user defined PEAR_Errors will go through the custom error handler outlined in the previous section. To properly utilize the added information from the PEAR_Error object used above, I’ve upgraded the error handler as follows:

function my_error_handler($errno$errstr$errfile$errline$context)
{

    
/* put all the information we have into a line which
     * will get logged */
    /* if we have extra info then log that too */    
if (is_array($context["userinfo"])){
            
$contextENV =
" - {$context["userinfo"]["class"]}->{$context["userinfo"]["method"]}()";
            
$errLogLine date("n/j/y H:i:s")." - {$context["userinfo"]["file"]}:{$context["userinfo"]["line"]}{$contextENV}{$errstr}\n";
    } else {
        
$errLogLine 
date("n/j/y H:i:s")." - {$errfile}:{$errline}{$errstr}\n";
    }

    
/* write it to the log */
error_log ($errLogLine3"/tmp/my-errors.log");

    
/* if it is not a trivial warning or a user triggered
* error then */
    /* redirect and exit */
    
if ($errno != E_NOTICE && $errno E_USER_ERROR){
        
/* bring the user to the happy error page & stop the
* script */
        
header("Location: http://HappyPage.php");
        
ob_end_clean();
        exit;
    }
/* end error handler function */

This function does essentially the same things as my previous handler, but you’ll notice I log the data that I stored into the localInfo hash in the earlier example. I also make sure not to end processing if a notice or a user triggered error occurs.

This function can be expanded in many creative and potentially useful ways. For example, you can have your code e-mail the administrator when a really bad error occurs. Customize this as necessary to meet your applications requirements.

Conclusions

As you’ve seen, implementing robust error handling isn’t that hard after some initial setup. A few lines of code here and there can not only spare your users those nasty PHP error messages, but can make debugging your code a whole lot easier for you. I hope that the steps above will allow you to implement effective, elegant error handling into your code without too much effort!

About the author

Justin Eckhouse has been developing in PHP for four years, and has been in the Web development game a lot longer than that. Justin is a technical product manager at Verge Works, a stellar IT and Web development shop in Oakland, CA. He is a lifetime entrepreneur, and gadget geek. Questions? Job offers? Send them to php@eckhouse.com.

Comments