- Handling Errors
- Handling External Errors
- Exceptions
- When to Use Exceptions
- Further Reading
Handling External Errors
Although we have called what we have done so far in this chapter error handling, we really haven't done much handling at all. We have accepted and processed the warning messages that our scripts have generated, but we have not been able to use those techniques to alter the flow control in our scripts, meaning that, for all intents and purposes, we have not really handled our errors at all. Adaptively handling errors largely involves being aware of where code can fail and deciding how to handle the case when it does. External failures mainly involve connecting to or extracting data from external processes.
Consider the following function, which is designed to return the passwd file details (home directory, shell, gecos information, and so on) for a given user:
<?php function get_passwd_info($user) { $fp = fopen("/etc/passwd", "r"); while(!feof($fp)) { $line = fgets($fp); $fields = explode(";", $line); if($user == $fields[0]) { return $fields; } } return false; } ?>
As it stands, this code has two bugs in it: One is a pure code logic bug, and the second is a failure to account for a possible external error. When you run this example, you get an array with elements like this:
<?php print_r(get_passwd_info('www')); ?> Array ( [0] => www:*:70:70:World Wide Web Server:/Library/WebServer:/noshell )
This is because the first bug is that the field separator in the passwd file is :, not ;. So this:
$fields = explode(";", $line);
needs to be this:
$fields = explode(":", $line);
The second bug is subtler. If you fail to open the passwd file, you will generate an E_WARNING error, but program flow will proceed unabated. If a user is not in the passwd file, the function returns false. However, if the fopen fails, the function also ends up returning false, which is rather confusing.
This simple example demonstrates one of the core difficulties of error handling in procedural languages (or at least languages without exceptions): How do you propagate an error up to the caller that is prepared to interpret it?
If you are utilizing the data locally, you can often make local decisions on how to handle the error. For example, you could change the password function to format an error on return:
<?php function get_passwd_info($user) { $fp = fopen("/etc/passwd", "r"); if(!is_resource($fp)) { return "Error opening file"; } while(!feof($fp)) { $line = fgets($fp); $fields = explode(":", $line); if($user == $fields[0]) { return $fields; } } return false; } ?>
Alternatively, you could set a special value that is not a normally valid return value:
<?php function get_passwd_info($user) { $fp = fopen("/etc/passwd", "r"); if(!is_resource($fp)) { return -1; } while(!feof($fp)) { $line = fgets($fp); $fields = explode(":", $line); if($user == $fields[0]) { return $fields; } } return false; } ?>
You can use this sort of logic to bubble up errors to higher callers:
<?php function is_shelled_user($user) { $passwd_info = get_passwd_info($user); if(is_array($passwd_info) && $passwd_info[7] != '/bin/false') { return 1; } else if($passwd_info === -1) { return -1; } else { return 0; } } ?>
When this logic is used, you have to detect all the possible errors:
<?php $v = is_shelled_user('www'); if($v === 1) { echo "Your Web server user probably shouldn't be shelled.\n"; } else if($v === 0) { echo "Great!\n"; } else { echo "An error occurred checking the user\n"; } ?>
If this seems nasty and confusing, it's because it is. The hassle of manually bubbling up errors through multiple callers is one of the prime reasons for the implementation of exceptions in programming languages, and now in PHP5 you can use exceptions in PHP as well. You can somewhat make this particular example work, but what if the function in question could validly return any number? How could you pass the error up in a clear fashion then? The worst part of the whole mess is that any convoluted error-handling scheme you devise is not localized to the functions that implement it but needs to be understood and handled by anyone in its call hierarchy as well.