Документация
HTML CSS PHP PERL другое

Exceptions

 
Previous
Table of Contents
Next

Exceptions

PHP5 has two error-handling systems that give us many choices when writing our web applications. Previously, you saw the first system based around errors, codes, and messages; now, this book will discuss the second systemexceptions.

Exception Basics

You can indicate that an error has occurred during the execution of your function by returning a special value, such as FALSE, to tell the caller that something unexpected has happened, such as the following:

<?php

function get_day_of_week($in_day)
{
  if (!is_int($in_day))
    return FALSE;

  if ($in_day < 0 || $in_day > 6)
    return FALSE;

  switch ($in_day)
  {
    case 0:
      return get_localized_string('sunday'); 
    case 1:
      return get_localized_string('monday'); 
    case 2:
      return get_localized_string('tuesday'); 
    case 3:
      return get_localized_string('wednesday'); 
    case 4:
      return get_localized_string('thursday'); 
    case 5:
      return get_localized_string('friday'); 
    case 6:
      return get_localized_string('saturday'); 
  }

  // never reached.
}

?>

While it is easy to code up, this system has a number of drawbacks:

  • Without adequate documentation, it is difficult to know what return values a function will have, and which of those are considered "error conditions."

  • There is nothing compelling callers to check the result of the function for a valid value.

  • It is challenging for users to know why the function failed and returned this result code. FALSE is not particularly informative.

In the face of these drawbacks, a new system for error reporting called structured exception handling was developed in various programming languages. Recently, PHP5 added support for its own flavor of exceptions.

Exceptions are generated by applications throwing (or raising) an exception whenever an error condition occurs. This exception interrupts the code execution and causes the function to immediately exit and return to the calling function. If the function wants to and knows how to catch (handle) the exception, it can. If not, the function is interrupted and the exception continues back up the call stack (the hierarchy of currently nested and executing functions) until it finds either somebody to catch the exception, or no more functions on the call stack. In this case, a default exception handler is executed (and the exception is said to be unhandled).

Exceptions are simply objectseither instances of or classes inheriting from the Exception classthat are passed around (as part of all the throwing going on) by the exception scheme. This gives us the advantages of passing around more robust information, especially if we create new classes, such as FileNotFoundException, DatabaseConnException, or UserAuthFailedException. The exception system also ensures that calling functions deal with the exceptions that our function can generate. If they do not, their entire application will grind to a halt.

The basic structure of the built-in Exception class is as follows:

<?php

class Exception
{
  // exception message  clients use getMessage() to query 
  protected $message = 'Unknown exception';

  // user defined exception code  clients use getCode()
  protected $code = 0;

  // source filename of exception  use getFile()
  protected $file;

  // source line of exception  use getLine()
  protected $line; 

  function __construct($message = null, $code = 0);

  //
  // remember: final functions cannot be overridden
  //
  public final function getMessage();
  public final function getCode();
  public final function getFile();
  public final function getLine();
  // this returns, as an array, the call stack of the 
  // location where the exception occurred.
  public final function getTrace();

  // this returns, as a string, the call stack of the
  // location where the exception occurred.
  public final function getTraceAsString();

  // this can be overridden by inheriting classes
  public function __toString();
}

?>

The protected member variables can be used and queried by inheriting classes, but not by external users of the Exception class. Various public functions are exposed to them, but these are marked as final so that inheriting classes cannot override them.

This book now looks at how exceptions are used in your applications.

Working with Exceptions

You can use the Exception class and the throw keyword to throw (raise or generate) an exception within PHP, as follows:

<?php

function get_day_of_week($in_day)
{
  if (!is_int($in_day))
    throw new Exception('Parameter is not an integer day');
  if ($in_day < 0 || $in_day > 6)
    throw new Exception('Day value must be between 0 and 6');

  // etc.
}

?>

Code that calls this function is expected to catch these exceptions by using a construct new to PHP5 called a try/catch block. In effect, this construct says to try to execute a block of code, and catch any exceptions that are thrown during its execution, such as the following:

<?php

try
{
  $day = get_day_of_week($dayvalue);
}
catch (Exception $e)
{
  echo 'An exception was thrown: ' . $e->getMessage();
}

?>

You must specify the type of exception that you would like to trap and give it a variable name ($e in the previous example) along with the catch keyword so that you can refer to it in the code. By specifying that we would like to catch anything that is of type Exception, we are indicating that we will catch any exceptions that are thrown. Later in this chapter, the "Extending Exceptions" section shows you how to catch specific exceptions in different blocks of code.

As mentioned previously, exceptions work their way up the call stack until they are caught and interrupt the execution of any functions along the way. Consider the following sequence of code:

<?php

function call_and_catch()
{
  try 
  {
    call_2();
    echo 'Eeeek!';
  }
  catch (Exception $e)
  {
    echo 'There was an error: ' . $e->getMessage();
  }
}

function call_2()
{
  call_3();
  echo 'Oink!';
}

function call_3()
{
  bad_function();
  echo 'Baa-aaaa!';
}

function bad_function()
{
  throw new Exception('¡Aieeee! ¡No es bueno!');
}

//
// this calls a bunch of functions, one of which throws an
// exception.
//
call_and_catch();

?>

In the preceding snippet of code, the call_and_catch function calls a function that in turn calls another that in turn calls another. The last function (call_3) calls the bad_function function, which results in a call stack as follows:

call_and_catch   // the top of the call stack
|--call_2
  |--call_3
    |-- bad_function  // bottom of the call stack

When the bad_function function throws the exception, PHP starts looking up the stack for somebody to catch it. It does not find a try-catch statement in call_3 or in call_2. It only sees what it is looking for in call_and_catch, where it then executes the catch code block. Because exceptions interrupt the execution of functions when they are thrown, the preceding script produces the following output:

There was an error:  ¡Aieeee! ¡No es bueno!

In most cases, you create a new instance of an Exception object and throw it. However, there are situations when you are given an exception object that you can rethrow as follows:

throw $e;

This is most commonly seen in catch blocks, as follows:

$resource = NULL;

try
{
  $resource = open_resource();
  do_something_with_resource($resource);
}
catch (Exception $e)
{
  // on failure, clean up the resource and re-throw
  if ($resource !== NULL)
    close_resource($resource);
  throw $e;
}

// normal processing -- just continue as normal.
close_resource($resource);

Unhandled Exceptions

If the call_and_catch function had not caught the exception in the previous example, the default exception handler would have been called. This function displays a message along the following lines:

Fatal error: Uncaught exception 'Exception' with message
  '¡Aieeee! ¡No es bueno!' in /home/httpd/www/trycatch.php:27
Stack trace:
 #0 /home/httpd/www/trycatch.php(27): bad_function()
 #1 /home/httpd/www/trycatch.php(22): bad_function()
 #2 /home/httpd/www/trycatch.php(17): call_3()
 #3 /home/httpd/www/trycatch.php(7): call_2()
 #4 /home/httpd/www/trycatch.php(34): call_and_catch()
 #5 {main} thrown in /home/httpd/www/trycatch.php on line 27

Like normal PHP error handling code, the exception system allows us to register a default exception handler using the set_exception_handler function (rather than the set_error_handler function). This function is called whenever an exception is thrown that none of the functions on the call stack catches.

<?php

function exception_handler($in_exception)
{
  $msg = $in_exception->getMessage();

  echo <<<EOTABLE

  <table width='80%' border='1' align='center' bgcolor='red'>
  <tr>
    <td>
      <img src='kaboom.png' border='0'/>
    </td>
    <td align='center'>
      We are sorry, but a fatal error has occurred while
      executing the application.  The system administrators
      have been notified and we are looking into the problem.
      <br/>
      We thank you for your patience, and urge you to try back
      again in a few hours.
      <br/>
      The error message was: $msg
    </td>
  </tr>
  </table>

EOTABLE;

  //
  // log the error.
  //
  error_log('UNHANDLED EXCEPTION: '
            . $msg . ' '
            . $in_exception->getFile() . ' '
            . $in_exception->getLine() . ' '
            . $in_exception->getCode(), 1);

  // 
  // send our administrators a piece of e-mail
  //
  error_log('Fatal Unandled Exception  See log',
            2, 'sysadmin@localhost');
}
//
// install our new exception handler.
//
set_exception_handler('exception_handler');

?>

When an unhandled exception is detected now, an entry is written to a log file, an e-mail is sent to our administrators, and the user sees something less cryptic.

Extending Exceptions

So far, one of the problems with our exception system is all of the exceptions are instances of the same class that only differ by the message they display. This gives us little chance to distinguish between different errors, especially if we localize the error messages into different languages.

It would be beneficial for you to create subclasses of the Exception class that you can then check in code to learn more about the nature of the error:

<?php

class ArgumentTypeException extends Exception
{
  function __construct($in_type, $in_expected)
  {
    parent::__construct("Expected: $in_expected," 
                        . "Received: $in_type"); 
  }
}

class ArgumentRangeException extends Exception
{
  function __construct($in_value, $in_bottom, $in_top)
  {
    $msg = <<<EOM
Value $in_value was not in the range $in_bottom .. $in_top.
EOM;
    parent::__construct($msg);
  }
}

?>

We can now update your get_day_of_week function to give you more information in case of a failure:

<?php

function get_day_of_week($in_day)
{
  if (!is_int($in_day))
    throw new ArgumentTypeException(gettype($in_day), 'int');
  if ($in_day < 0 || $in_day > 6)
    throw new ArgumentRangeException($in_day, 0, 6);

  // etc.
}

?>

Now that you can create more specific exceptions, you can also trap specific types of exceptions by specifying multiple catch blocks with one TRy:

<?php

try
{
  $conn = connect_to_database();
}
catch (ServerBusyException $sbe)
{
  echo <<<EOM
  The server appears to be too busy.  Please try again in a few
  minutes.
EOM;
}
catch (DatabaseErrorException $dbe)
{
  echo <<<EOM
  An internal error with the database server has occurred and
  we are unable to continue.  System Administrators are looking
  into the problem.
EOM;

  error_log('database auth failed: ' . $dbe->getMessage(),
            3, '../logs/auth.log');
}
catch (Exception $e)
{
  echo <<<EOM
  An unexpected error has occurred.  We are investigating the
  problem.
EOM;
  error_log('unexpected error: ' $e->getMessage(), 1);
}

?>

When multiple catch blocks are seen, PHP evaluates them one at a time in the order of receipt until it finds one that will work with the given exception. Thus, you can see that the order is important in the preceding example. If the block beginning with

catch (Exception $e)

had been first, then all exceptions would match against it and none of the others would be executed.


Previous
Table of Contents
Next
© 2000- NIV