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 ($errLogLine, 3, "/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();
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 */
}
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",
101, PEAR_ERROR_TRIGGER, E_USER_NOTICE,
$localInfo);
}
}/* end ATM class */
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 objectgetCallback()-Get the callback function/method from an error objectgetMessage()-Get the error message from an error objectgetCode()-Get error code from an error objectgetUserInfo()-Get additional user-supplied information
$teller = new ATM();
$initResult = $teller->initATM("a123.33", 123, 341);
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";
}
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 ($errLogLine, 3, "/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 */
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.

Comments