Team LiB
Previous Section Next Section

Putting It All Together

Finally, let's consider a short script that performs several common taskssome of which may fail and cause errors of their own and others which, although they succeed, yield errorsome results.

<?php
 set_error_handler('user_errors', E_USER_WARNING | E_USER_ERROR);
 ini_set('error_reporting', 0);
 session_start();
 $errors = array();

 $db_conn = @mysql_connect('localhost','username','password');
 if ($db_conn === false) {
  trigger_error("Unable to connect to database.", E_USER_ERROR);
 }
 
 if (isset($_REQUEST['submit'])) {
  if (empty($_REQUEST['username'])) {
   trigger_error("No username provided", E_USER_WARNING);
  }
  if (empty($_REQUEST['password'])) {
   trigger_error("No password provided", E_USER_WARNING);
  }
  $result = @mysql_query(sprintf(
       "SELECT userid FROM users WHERE username='%s' AND password='%s'",
       addslashes($_REQUEST['username']),
       addslashes($_REQUEST['password'])), $db_conn);
  if ($result === false) {
   trigger_error("Unable to login, database error", E_USER_ERROR);
  }
  if (mysql_num_rows($result) == 0) {
    trigger_error("Login failed. Username or Password mismatch.", 
                  E_USER_WARNING);
  } else {
   list($_SESSION['userid']) = mysql_fetch_row($result);
  }
 }
?><html>
<head>
<title>Simple Login Script</title>
</head>
<body>
<?php 
 if (count($errors) > 0) {
  echo "The following warnings were encountered:<br/>";
  echo "<ul>\n";
  foreach($errors as $error) {
   echo "<li>" . htmlspecialchars($error) . "</li>\n";
  }
  echo "</ul>\n";
 }
 if ($_SESSION['userid']) {
   echo "You are logged in as user id #{$_SESSION['userid']}";
 } else { 
?><form method="POST">
Username: <input type="text" name="username" /><br />
Password: <input type="password" name="password" /><br />
<input type="submit" name="submit" value="Log In" />
</form>
<?php } ?>
</body>
</html>
<?php
function user_errors($errno, $errstr, $filename, $lineno, &$context) {
 if ($errno == E_USER_WARNING) {
  $context['errors'][] = $errstr;
  return true;
 }
 echo "<html><head><title>A fatal error has occurred</title></head><body>";
 echo htmlspecialchars($errstr);
 echo "</body></html>";
}
?>

Lets go over this script section by section:

set_error_handler('user_errors', E_USER_WARNING | E_USER_ERROR);
ini_set('error_reporting', 0);
session_start();
$errors = array();

We define user_errors() as our custom error handler that should receive all E_USER_WARNING and E_USER_ERROR messages; next, we effectively suppress all other errors in the default error handler by reducing error_reporting to 0. Because we'll be using a session to store our logged in user ID, we call session_start(). Last, we initialize an array for storing warnings as they are encountered.

$db_conn = @mysql_connect('hostname','username','password');
if ($db_conn === false) {
 trigger_error("Unable to connect to database.", E_USER_ERROR);
}

Next we attempt to connect to a database server. We know from the documentation of mysql_connect() that if the connection fails for any reason, it will raise an E_WARNING and return false. If a failure does occur, we want to suppress the default error but prevent any further processing by erroring out with a user-friendly message.

if (empty($_REQUEST['username'])) {
 trigger_error("No username provided", E_USER_WARNING);
}
if (empty($_REQUEST['password'])) {
 trigger_error("No password provided", E_USER_WARNING);
}

If this is a login action, indicated by the presence of $_REQUEST['submit'], we check to make sure a username and password were set. If either were not, we raise an E_USER_WARNING with a descriptive message. Because this severity is not fatal, processing will continue.

$result = @mysql_query(sprintf(
     "SELECT userid FROM users WHERE username='%s' AND password='%s'",
     addslashes($_REQUEST['username']),
     addslashes($_REQUEST['password'])), $db_conn);
if ($result === false) {
 trigger_error("Unable to login, database error", E_USER_ERROR);
}
if (mysql_num_rows($result) == 0) {
 trigger_error("Login failed. Username or Password mismatch.", E_USER_WARNING);
} else {
 list($_SESSION['userid']) = mysql_fetch_row($result);
}

A database query is now executed, looking for a user ID with a matching username and password. Again the result of the database function is checked and an E_USER_ERROR is raised if the query failed. Finally, if a user ID was returned, it is populated into a session variable; otherwise, an E_USER_WARNING is raised stating the login was unsuccessful.

if (count($errors) > 0) {
 echo "The following warnings were encountered:<br/>";
 echo "<ul>\n";
 foreach($errors as $error) {
  echo "<li>" . htmlspecialchars($error) . "</li>\n";
 }
 echo "</ul>\n";
}

At last, we output some HTML and open the body for content. If any E_USER_WARNING errors were raised, our custom error handler would have populated the text of the error messages into the $errors variable by way of the pass-by-ref $context parameter to our error function. Here in the body of our content, we iterate through that array to display the warnings in an unnumbered bulleted list. Then, depending whether we are logged in, we display the user ID or a login form.

function user_errors($errno, $errstr, $filename, $lineno, &$context) {
 if ($errno == E_USER_WARNING) {
  $context['errors'][] = $errstr;
  return true;
 }
 if ($errno == E_USER_ERROR) {
  echo "<html><head><title>A fatal error has occurred</title></head><body>";
  echo htmlspecialchars($errstr);
  echo "</body></html>";
  return true;
 }
 return false;
}

The last thing our script does is to define the error handler named in our call to set_error_handler(). User-triggered warnings are handled by adding the error string to an $errors variable in the scope that was active at the time the error was raised. Because all our errors are raised in the global scope, using $context['errors'] is effectively identical to using $GLOBALS['errors']. Fatal E_USER_ERROR conditions are dealt with by wrapping the error string message in a simple HTML template. If we handle our error, we return true; otherwise, we return false.

    Team LiB
    Previous Section Next Section