IN THIS CHAPTER, WE EXPLAIN THE CONCEPT OF exception handling and the way it is implemented in PHP. Exceptions provide a unified mechanism for handling errors in an extensible, maintainable, and object-oriented way.
Key topics covered in this chapter include
Exception control structures: try…throw…catch
Exceptions in Bob’s Auto Parts
Exceptions and PHP’s other error handling mechanisms
The basic idea of exception handling is that code is executed inside what is called a try
block. That’s a section of code that looks like this:
try
{
// code goes here
}
If something goes wrong inside the try
block, you can do what is called throwing an exception. Some languages, such as Java, throw exceptions automatically for you in certain cases. In PHP, exceptions must be thrown manually. You throw an exception as follows:
throw new Exception('message', code);
The keyword throw
triggers the exception handling mechanism. It is a language construct rather than a function, but you need to pass it a value. It expects to receive an object. In the simplest case, you can instantiate the built-in Exception
class, as done in this example.
The constructor for this class takes two parameters: a message and a code. They are intended to represent an error message and an error code number. Both of these parameters are optional.
Finally, underneath your try
block, you need at least one catch
block. A catch
block looks like this:
catch (typehint exception)
{
// handle exception
}
You can have more than one catch
block associated with a single try
block. Using more than one would make sense if each catch
block is waiting to catch a different type of exception. For example, if you want to catch exceptions of the Exception
class, your catch
block might look like this:
catch (Exception $e)
{
// handle exception
}
The object passed into (and caught by) the catch
block is the one passed to (and thrown by) the throw
statement that raised the exception. The exception can be of any type, but it is good form to use either instances of the Exception
class or instances of your own user-defined exceptions that inherit from the Exception
class. (You see how to define your own exceptions later in the chapter.)
When an exception is raised, PHP looks for a matching catch
block. If you have more than one catch
block, the objects passed in to each should be of different types so that PHP can work out which catch
block to fall through to.
One other point to note is that you can raise further exceptions within a catch
block.
To make this discussion a bit clearer, let’s look at an example. A simple exception handling example is shown in Listing 7.1.
<?php
try {
throw new Exception("A terrible error has occurred", 42);
}
catch (Exception $e) {
echo "Exception ". $e->getCode(). ": ". $e->getMessage()."<br />".
" in ". $e->getFile(). " on line ". $e->getLine(). "<br />";
}
?>
In Listing 7.1, you can see that we used a number of methods of the Exception
class, which we discuss shortly. The result of running this code is shown inFigure 7.1.
In the sample code, you can see that we raise an exception of class Exception
. This built-in class has methods you can use in the catch
block to report a useful error message.
Exception
ClassPHP has a built-in class called Exception
. The constructor takes two parameters, as we discussed previously: an error message and an error code.
In addition to the constructor, this class comes with the following built-in methods:
getCode()
—Returns the code as passed to the constructor
getMessage()
—Returns the message as passed to the constructor
getFile()
—Returns the full path to the code file where the exception was raised
getLine()
—Returns the line number in the code file where the exception was raised
getTrace()
—Returns an array containing a backtrace where the exception was raised
getTraceAsString()
—Returns the same information as getTrace
, formatted as a string
__toString()
—Allows you to simply echo an Exception
object, giving all the information from the above methods
You can see that we used the first four of these methods in Listing 7.1. You could obtain the same information (plus the backtrace) by executing
echo $e;
A backtrace shows which functions were executing at the time the exception was raised.
Instead of instantiating and passing an instance of the base Exception
class, you can pass any other object you like. In most cases, you will extend the Exception
class to create your own exception classes.
You can pass any other object with your throw
clause. You may occasionally want to do this if you are having problems with one particular object and want to pass it through for debugging purposes.
Most of the time, however, you will extend the base Exception
class. The PHP manual provides code that shows the skeleton of the Exception
class. This code, taken from http://us.php.net/manual/en/language.oop5.php, is reproduced in Listing 7.2. Note that this is not the actual code but represents what you can expect to inherit.
<?php
class Exception {
function __construct(string $message=NULL, int $code=0) {
if (func_num_args()) {
$this->message = $message;
}
$this->code = $code;
$this->file = __FILE__; // of throw clause
$this->line = __LINE__; // of throw clause
$this->trace = debug_backtrace();
$this->string = StringFormat($this);
}
protected $message = "Unknown exception"; // exception message
protected $code = 0; // user defined exception code
protected $file; // source filename of exception
protected $line; // source line of exception
private $trace; // backtrace of exception
private $string; // internal only!!
final function getMessage(){
return $this->message;
}
final function getCode() {
return $this->code;
}
final function getFile() {
return $this->file;
}
final function getTrace() {
return $this->trace;
}
final function getTraceAsString() {
return self::TraceFormat($this);
}
function _toString() {
return $this->string;
}
static private function StringFormat(Exception $exception) {
// … a function not available in PHP scripts
// that returns all relevant information as a string
}
static private function TraceFormat(Exception $exception) {
// … a function not available in PHP scripts
// that returns the backtrace as a string
}
}
? >
The main reason we are looking at this class definition is to note that most of the public methods are final: That means you cannot override them. You can create your own subclass Exceptions
, but you cannot change the behavior of the basic methods. Note that you can override the __toString()
function, so you can change the way the exception is displayed. You can also add your own methods.
An example of a user-defined Exception
class is shown in Listing 7.3.
<?php
class myException extends Exception
{
function __toString()
{
return "<table border="1">
<tr>
<td><strong>Exception ".$this->getCode()."
</strong>: ".$this->getMessage()."<br />"."
in ".$this->getFile()." on line ".$this->getLine()."
</td>
</tr>
</table><br />";
}
}
try
{
throw new myException("A terrible error has occurred", 42);
}
catch (myException $m)
{
echo $m;
}
?>
In this code, you declare a new exception class, called myException
, that extends the basic Exception
class. The difference between this class and the Exception
class is that you override the __toString()
method to provide a “pretty” way of printing the exception. The output from executing this code is shown in Figure 7.2.
This example is fairly simple. In the next section, we look at ways to create different exceptions to deal with different categories of error.
Chapter 2, “Storing and Retrieving Data,” described how you could store Bob’s order data in a flat file. You know that file I/O (in fact, any kind of I/O) is one area in programs where errors often occur. This makes it a good place to apply exception handling.
Looking back at the original code, you can see that three things are likely to go wrong with writing to the file: the file cannot be opened, a lock cannot be obtained, or the file cannot be written to. We created an exception class for each of these possibilities. The code for these exceptions is shown in Listing 7.4.
<?php
class fileOpenException extends Exception
{
function __toString()
{
return "fileOpenException ". $this->getCode()
. ": ". $this->getMessage()."<br />"." in "
. $this->getFile(). " on line ". $this->getLine()
. "<br />";
}
}
class fileWriteException extends Exception
{
function __toString()
{
return "fileWriteException ". $this->getCode()
. ": ". $this->getMessage()."<br />"." in "
. $this->getFile(). " on line ". $this->getLine()
. "<br />";
}
}
class fileLockException extends Exception
{
function __toString()
{
return "fileLockException ". $this->getCode()
. ": ". $this->getMessage()."<br />"." in "
. $this->getFile(). " on line ". $this->getLine()
. "<br />";
}
}
?>
These Exception
subclasses do not do anything particularly interesting. In fact, for the purpose of this application, you could leave them as empty subclasses or use the provided Exception
class. We have, however, provided a __toString()
method for each of the subclasses that explains what type of exception has occurred.
We rewrote the processorder.php
file from Chapter 2 to incorporate the use of exceptions. The new version is shown in Listing 7.5.
<?php
require_once("file_exceptions.php");
// create short variable names
$tireqty = $_POST['tireqty'];
$oilqty = $_POST['oilqty'];
$sparkqty = $_POST['sparkqty'];
$address = $_POST['address'];
$DOCUMENT_ROOT = $_SERVER['DOCUMENT_ROOT'];
?>
<html>
<head>
<title>Bob’s Auto Parts - Order Results</title>
</head>
<body>
<h1>Bob’s Auto Parts</h1>
<h2>Order Results</h2>
<?php
$date = date('H:i, jS F'),
echo "<p>Order processed at ".$date."</p>";
echo '<p>Your order is as follows: </p>';
$totalqty = 0;
$totalqty = $tireqty + $oilqty + $sparkqty;
echo "Items ordered: ".$totalqty."<br />";
if( $totalqty == 0) {
echo "You did not order anything on the previous page!<br />";
} else {
if ( $tireqty > 0 ) {
echo $tireqty." tires<br />";
}
if ( $oilqty > 0 ) {
echo $oilqty." bottles of oil<br />";
}
if ( $sparkqty > 0 ) {
echo $sparkqty." spark plugs<br />";
}
}
$totalamount = 0.00;
define('TIREPRICE', 100);
define('OILPRICE', 10);
define('SPARKPRICE', 4);
$totalamount = $tireqty * TIREPRICE
+ $oilqty * OILPRICE
+ $sparkqty * SPARKPRICE;
$totalamount=number_format($totalamount, 2, '.', ' '),
echo "<p>Total of order is ".$totalamount."</p>";
echo "<p>Address to ship to is ".$address."</p>";
$outputstring = $date." ".$tireqty." tires ".$oilqty." oil "
.$sparkqty." spark plugs $".$totalamount
." ". $address."
";
// open file for appending
try
{
if (!($fp = @fopen("$DOCUMENT_ROOT/../orders/orders.txt", 'ab')))
throw new fileOpenException();
if (!flock($fp, LOCK_EX))
throw new fileLockException();
if (!fwrite($fp, $outputstring, strlen($outputstring)))
throw new fileWriteException();
flock($fp, LOCK_UN);
fclose($fp);
echo "<p>Order written.</p>";
}
catch (fileOpenException $foe)
{
echo "<p><strong>Orders file could not be opened.
Please contact our webmaster for help.</strong></p>";
}
catch (Exception $e)
{
echo "<p><strong>Your order could not be processed at this time.
Please try again later.</strong></p>";
}
?>
</body>
</html>
You can see that the file I/O section of the script is wrapped in a try
block. It is generally considered good coding practice to have small try
blocks and catch the relevant exceptions at the end of each. This makes your exception handling code easier to write and maintain because you can see what you are dealing with.
If you cannot open the file, you throw a fileOpenException
; if you cannot lock the file, you throw a fileLockException
; and if you cannot write to the file, you throw a fileWriteException
.
Look at the catch
blocks. To illustrate a point, we have included only two: one to handle fileOpenException
s and one to handle Exception
s. Because the other exceptions inherit from Exception
, they will be caught by the second catch
block. Catch
blocks are matched on the same basis as the instanceof
operator. This is a good reason for extending your own exception classes from a single class.
One important warning: If you raise an exception for which you have not written a matching catch block, PHP will report a fatal error.
In addition to the exception handling mechanism discussed in this chapter, PHP has complex error handling support, which we consider in Chapter 26, “Debugging.” Note that the process of raising and handling exceptions does not interfere or prevent this error handling mechanism from operating.
In Listing 7.5, notice how the call to fopen()
is still prefaced with the @
error suppression operator. If it fails, PHP will issue a warning that may or may not be reported or logged depending on the error reporting settings in php.ini
. These settings are discussed at length in Chapter 26, but you need to know that this warning will still be issued regardless of whether you raise an exception.
Because exception handling is new to PHP, not much has been written on the subject. However, basic information about exception handling is plentiful. Sun has a good tutorial about what exceptions are and why you might want to use them (written from a Java perspective, of course) at http://java.sun.com/docs/books/tutorial/essential/exceptions/handling.html.
The next part of the book deals with MySQL. We explain how to create and populate a MySQL database and then link what you’ve learned to PHP so that you can access your database from the Web.