Defensive Driving With PHP

If you find this code useful and would like to encourage me to post more goodies here, you could buy me something from my wish lists at Amazon.com or at MusicDirect.com

One of the common mistakes I see many novice PHP developers make is to assume that everything should work as coded. Eventually we all learn via the school of hard knocks that such assumptions are not worth the initial keystrokes they may save in our source code, especially after spending a late night debugging session only to find out it was a simple typo that kept a script from working. In this article I hope to provide you with a few of the defensive coding tips and tricks I've picked up during my attendance in hard knock classes.

Danger, Will Robinson!

Step one for becoming more defensive is to turn on all error reporting from the PHP processor. This is done by simply adding this line to the beginning of the script:

ini_set('display_errors', 1);
error_reporting(E_ALL);

When error_reorting is set to E_ALL, the PHP parser reports everything that it finds suspicious, not just fatal errors. Later, as your code solidifies, you may choose to set the reporting down to a lower level and instead of dispalying errors send them to a log file, instead. (See www.php.net/error_reporting for more details.)

Die(), you @#^%$!!!

When initially developing a script, make generous use of the die() command. When doing so, use the space between the parentheses to output as much stuff as you can think of that might help you debug. To throw a fatal error for a function when it returns FALSE or zero, you can use "or" as a convenient shorthand:

$connx = mysql_connect($database, $user, $password) or
    die("Unable to connect to database $database as user $user. " . mysql_error());

Note that I have included some of the variables in the error message to help see if something is wrong with them (while deciding not to output the password value for security reasons), and have appended a call to mysql_error() in case any error was returned by the database.

Sometimes you may think of a fatal problem that will not be flagged by a FALSE return from a function. You can still use die(), but you will need a bit more code to catch the error condition.

if(empty($_POST['name'] or empty($_POST['password'])
{
  die("Missing name ('{$_POST['name']}') and/or password (" .
      strlen($_POST['password'] . " characters");
}

This sort of coding will lead you right to the source of the problem, as opposed to getting one of those "invalid resource ID" messages 20 lines further down when your database query fails.

Don't go away mad—just go away.

As everything finally starts to come together and your script is ready to leave the safety of your PC for the big, bad world of the internet, you likely would prefer not to have ugly die() messages plastered across a page when it fails for some reason beyond your control. (I'm sure it will not be because of an error you made.)

Let's not just throw all those die()'s away, though. Instead we can convert them into more user-friendly functions that will still help us debug any problems not discovered during internal testing. Two things need to be changed:

  1. We want to output a "nice" error message that looks professional and which implies that everything is under control and we're sure it will be better in the morning.
  2. We want to capture that debug information somewhere where it can be reviewed later.

So let's create a new function called error():

function error($debug, $fatal = FALSE)
# save error info to log file, if fatal terminate the script
{
  # generic death message:
  $msg = <<<EOD
<p class="error">We're sorry, but an unrecoverable error occurred processing
your request. If this problem persists, please contact the
<a href="mailto:{$_SERVER['SERVER_ADMIN']}">webmaster</a>.</p>
EOD;
  # log file named for current file:
  $path = "C:\\"; # specify where log files will be saved
  $this = array_pop(explode("/", $_SERVER['PHP_SELF']));
  $file = "$path$this.log";
  # write debug data to log file:
  error_log(date("Y/m/d-h:m:s") . " --> $debug\n", 3, $file);
  if($fatal)
  {
    die($msg);  # die if a fatal error
  }
  return(TRUE); # otherwise return
}

Now you can include() this function, and wherever you currently have die("some message") just change it to error("some message"[, TRUE|FALSE]), where the optional second parameter specifies whether the error is fatal (TRUE) or not. As a finishing touch, add a class ".error" to your CSS style sheets to give the fatal error message the desired appearance on your site.

And so it goes....

While I know I've only scratched the surface of error-handling in this article, I hope I have at least gotten you into a defensive frame of mind. For more information on options for PHP's error_log() function, see www.php.net/error_log. For additional error-handling options, see the Error Handling and Logging Functions section of the PHP Manual.