A Holistic Approach to Error Handling


The next major component of our web applications that we want to have planned and implemented in advance is how we handle errors. In addition to planning how we will manage errors and exceptions or how we will handle catastrophic failures in our applications, we need to distinguish between user errors and application errors.

User Versus Application Errors

Although it is tempting to think of anything that goes wrong as an "error," we can clearly distinguish between different "levels" of failure. Consider the following three examples:

  • 99991099 is not a valid Zip code in the United States.

  • The database is full and cannot accept more records.

  • PHP cannot start because of a configuration problem.

Three distinct logical "levels" of error are demonstrated here:

  • User errors (sometimes referred to as "pilot errors")

  • Errors in applications or the running thereof

  • Catastrophic failures in the software or hardware that operate web applications

As you learned in Chapter 18, "Error Handling and Debugging," you cannot do a whole lot about the third category from within your scripts; however, you can certainly deal with the first two (although in different ways). User errors are something we can tell the user about and let him correct before continuing; application errors are largely beyond the user's control.

The normal sequence of pages resulting from user input will look something like that shown in Figure 30-1. After the user inputs data and clicks a button to proceed, we go to a page to process this data. This script presents no user interface but serves to validate the input and take any appropriate action. This page either determines that there is an error in the data provided and sends the user back to the input page, or determines that everything went well and takes any appropriate action before sending the user on to some other page (whether it's the next page in a sequence of pages or some simple confirmation page).

Figure 30-1. The sequence of events for user input pages.


We would like to provide some feedback to users for those situations that cause us to send them back, so our form and input pages need to have a bit of extra logic to handle the error message. A trivial example of our form would be as follows, starting with a script called loginform.php:

 <!DOCTYPE html PUBLIC "~//W3C//DTD XHTML 1.0 Transitional//EN"   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"       xml:lang="en-US"> <head>   <title>User Name and Password </title>   <meta http-equiv="content-type"         content="text/html; charset=utf-8"/> </head> <?php   //   // if there was a login failure, mention that now.   //   if (isset($_GET['er']) and $_GET['er'] == 1)   {     $msg = <<<EOM     <font color='red'>       <br/>We're sorry, but the username and password provided       do match any available in our records.<br/>     </font> EOM;    }   else   {     $msg = '';   } ?> <body>   <form action='submitlogin.php' method='POST'>     <br/><?php echo $msg ?><br/>     Name: <input type='text' size='30' name='username'/><br/>     Password:     <input type='password' size='30' name='passwd'/><br/>     <input type='submit' value='Login'/>   </form> </body> </html> 

The submitlogin.php page then operates as follows:

 <?php ob_start(); require_once('../sessions.inc'); // // make sure we got data and that it is valid login // information. // if (!isset($_POST['username']) or !isset($_POST['passwd'])     or !validateLogin($_POST['username'], $_POST['passwd'])) {   header('Location: loginform.php?er=1');   ob_end_clean();   exit; } // // login was okay.  send the user to the confirmation page. // $_SESSION['username'] = $_POST['username']; header('Location: confirmed.php'); ob_end_clean(); exit; ?> 

Our final page, the confirmed.php file, is just some other page that assumes that the previous page in the series (that is, loginform.php) worked successfully:

 <?php // // if we haven't logged in yet, then don't come here! // if (!isset($_SESSION['username'])) {   header('Location: loginform.php');   exit; } else {   $uname = $_SESSION['username']; } echo <<<EOM <p align='center'>   Welcome to the system, <b>$uname</b>.  We're glad you came   back for another visit! </p> EOM; ?> 

Replacing the Default Handlers

Although we can help users work through problems with their input (and provide help wherever possible), we cannot help them through an error in our SQL code or a problem with the database server. For these, users will likely be unable to do anything. We need to tell them that the error has occurred and then direct them to somewhere safe.

The first thing we will do is write our own error handlers for both regular PHP errors and exceptions. The error handlers we will write will perform the following tasks:

  • Gather information about the error

  • Append the information to a log file that we specify

  • Redirect users to a new page that displays a more user-friendly error message and offers them suggestions about how to deal with the problem

The function to replace regular PHP errors will look like this:

 /**  *=-----------------------------------------------------------=  * app_error_handler  *=-----------------------------------------------------------=  * This function performs the default error handling for  * unhandled PHP errors such as E_ERROR, E_WARNING, E_NOTICE,  * etc.  We will direct to the error.php file as much as  * possible.  *  * Parameters:  *    $in_errno         - error number  *    $in_errstr        - text of message  *    $in_errfile       - script filename that generated msg  *    $in_errline       - line number in script of error  *    $in_errcontext    - may or may not contain the symbol  *                        table as it was at the time of the  *                        error.  */ function app_error_handler (   $in_errno,   $in_errstr,   $in_errfile,   $in_errline,   $in_errcontext ) {   /**    * If we already have an error, then do no more.    */   if (isset($_SESSION)       and (isset($_SESSION['errstr'])            or isset($_session['exception'])))   {     return;   }   /**    * first, we will log the error so we know about it.    */   error_log(date('c')             . " Unhandled Error ($in_errfile, $in_errline): "             . "$in_errno, '$in_errstr'\r\n", 3, LOGFILE_PATH);   /**    * if we have session information, send the user to more    * helpful pastures.    */   if (isset($_SESSION))   {     $_SESSION['errstr'] = "$in_errstr ($in_errfile, line $in_errline)";   }   header('Location: error.php'); } 

The function we will use to manage unhandled exceptions in our code will likewise look like this:

 /**  *=-----------------------------------------------------------=  * app_exception_handler  *=-----------------------------------------------------------=  * This is our default exception handler, which we will use to  * report the contents of uncaught exceptions.  Our web  * application will throw exceptions when it encounters fatal  * errors from which it cannot recover.  We will write log  * information about the error and attempt to send the user to  * a more helpful page where we can give him less scary  * messages ...  *  * Parameters:  *    $in_exception         - the exception that was thrown.  */ function app_exception_handler($in_exception) {   /**    * If we already have an error, then do no more.    */   if (isset($_SESSION)       and (isset($_SESSION['errstr'])            or isset($_session['exception'])))   {     return;   }   /**    * first, log the exception    */   $class = get_class($in_exception);   $file = $in_exception->getFile();   $line = $in_exception->getLine();   $msg = $in_exception->getMessage();   error_log(date('c')             . " Unhandled Exception: $class ($file, $line): "             . " $msg\r\n", 3, LOGFILE_PATH);   /**    * Now try to send the user to a better error page, saving    * more helpful information before we go.    */   if (isset($_SESSION))   {     $_SESSION['exception'] = $in_exception;   }   header('Location: error.php'); } 

Finally, we use the following code to ensure that these two functions are properly installed and used by PHP:

 /**  * Install these two new functions that we have written.  */ set_error_handler('app_error_handler'); set_exception_handler('app_exception_handler'); 

Displaying Errors to Users

After we have written the information to the logs, we redirect users to an error page, error.php. This file displays a more friendly and easily understood message to users and offers them some ideas as to how they might avoid the problem in the future, or at least apologizes profusely for the inconvenience.

Most of the errors that are not caused by user input are caused by coding errors in scripts or problems accessing other resources such as the disk or database servers. In these situations, the web application server continues to operate normally, which means we can still provide users a user interface similar to that used in the rest of your application.

We can thus write an error.php that is called by the error handlers we mentioned in "Replacing the Default Handlers," and tailor it to exactly how we want to explain the problem to users. We will start with a relatively simple one, as follows:

 <?php /**  *=-----------------------------------------------------------=  * error.php  *=-----------------------------------------------------------=  * Author: Marc Wandschneider  *  * This script is used to report unhandled errors in our  * web application.  It is typically sent here when there  * is an unhandled error or an unhandled exception.  *  * In both cases, the session data has hopefully been primed  * with data that we can use to print a more helpful message  * for the user.  */ ob_start(); $page_title = "Error"; require_once('posterstore/errors.inc'); require_once('posterstore/session.inc'); require_once('posterstore/pagetop.inc'); if (isset($_SESSION['exception'])) {   $exc = $_SESSION['exception'];   $msg = $exc->getMessage(); } else if (isset($_SESSION['errstr'])) {   $msg = $_SESSION['errstr']; } else if (!isset($_SESSION)) {   $msg = <<<EOM Unable to initialize the session.  Please verify that the session data directory exists. EOM; } else {   $msg = 'Unknown Error'; } /**  * Make sure that the next time an error occurs, we reset  * this error data.  */ unset($_SESSION['exception']); unset($_SESSION['errstr']); ?> <h2 align='center'>Unexpected Error</h2> <p align='center'>   We are very sorry, but an unexpected error has occurred in   the application.  This occurs either because a page was   used improperly and visited directly instead of through the   web site or because of a system error in the application.   The web site administrators have been   notified and will look into the problem as soon as possible.   We apologize for the inconvenience and kindly ask you to try   again or try back again in a little while. </p> <p align='center'>   Please click <a href='index.php'>here</a> to go back to the   main page and continue working with our system. </p> <p align='center'>   The error received was: <br/><br/>   <b><?php echo $msg ?></b> </p> <?php require_once('posterstore/pagebottom.inc'); ob_end_flush(); ?> 

Creating New Exception Classes

One of the primary ways in which we will signal errors that are not user-input problems in our application is through the use of exceptions. Although we could write code such as the following, one of the problems with the following snippet is that all of the exceptions are instances of the same class, and we have no more information than the simple text that we pass to the constructor. We also don't have any real way of distinguishing different types of errors.

 <?php $file = fopen('filename', 'r'); if (!$file)   throw new Exception('file error!'); /* some other code goes here ... */ $conn = @new mysqli(...); if (!$conn)   throw new Exception("DB ERR: " . mysqli_connect_error()); ?> 

By creating our own exceptions that inherit from the core Exception class instead, we can use the PHP language's type system to help us more clearly identify the source of a problem, provide more helpful text, and perhaps even provide localized messages depending on the exact needs of our application.

We will therefore create a number of new exception classes for each web application we write, similar to some of the following:

[View full width]

class DatabaseErrorException extends Exception { function __construct($in_errmsg) { parent::__construct("We're sorry, but an internal database error has occurred. Our system administrators have been notified and we kindly request that you try again in a little while. Thank you for your patience. ($in_errmsg)"); } } class InvalidArgumentException extends Exception { function __construct($in_argname) { parent::__construct("We're sorry, but an internal programming error has occurred in the web application. The system administrators have been notified of the error and we kindly request that you try again in a little while. (param: $in_argname)"); } } class InternalErrorException extends Exception { function __construct($in_msg) { parent::__construct("An Internal error in the web application has occurred. The site administrators have been notified and we kindly ask you to try back again in a bit. (Message: '$in_msg')"); } } ?>

We can then in code just throw exceptions of the appropriate type when we encounter the related problem:

 <?php function some_function($in_somevalue) {   if ($in_somevalue < 0)     throw new InvalidArgumentException('$in_somevalue');   $conn = new mysqli(...);   if (mysqli_connect_errno() !== 0)     throw new DatabaseErrorException(mysqli_connect_error());   // etc. ... } ?> 




Core Web Application Development With PHP And MYSQL
Core Web Application Development with PHP and MySQL
ISBN: 0131867164
EAN: 2147483647
Year: 2005
Pages: 255

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net