7.5 User Exceptions

 <  Day Day Up  >  

7.5 User Exceptions

Exceptions aren't restricted to extensions. You can trigger your own exceptions by throw ing an instance of the Exception class.

User-thrown exceptions are useful because they allow you to integrate the error handling for your own errors with exceptions thrown by PHP. Inside your methods , you may still need to check a return value; however, converting those errors into exceptions allows the developer using your classes to know he can reliably trap all errors using a try/catch block.

This section covers when it's appropriate to throw exceptions of your own, and also how to create and throw an exception. Chapter 10 contains an address book application that demonstrates , among other things, how to put these exception-handling techniques into practice.

7.5.1 When to Use Exceptions

There many categories of errors, and exceptions are not always an appropriate way to handle them in PHP. In general, there are three major groups:


Programming errors

You, the programmer, have made a mistake. For instance, you passed an incorrect number of arguments to a method.


Client code errors

Your code is okay, but there's a problem with an external resource. For instance, your code uses an XML file from a third-party web site, but that file isn't valid XML. Or, a person has tried to join your site, but has requested a username already in use.


Resource failures

Your code is failing because of a breakdown in communication with another process. For instance, you cannot connect to a third-party web site because of network traffic. Or, your database is refusing additional connections because it is overloaded.

Unlike Java, PHP does not use exceptions internally. Therefore, many programming errors still need to be trapped in the traditional manner. You can then choose to either handle them in place or create an exception for them.

Client code errors are also highly dependent on the extension. Many extensions also do not throw exceptions or they throw them only when you use their object-oriented interface instead of their procedural interface. However, it makes sense to use exceptions when dealing with client code errors.

Most PHP programs don't try to recover from resource failures. If a web site or database doesn't respond the first time, it's usually assumed to be down. Sometimes it makes sense to retry the connection, but that can often be handled more easily without exceptions.

Exceptions are not an object-oriented version of goto . They're not for flow control; they're for handling exceptional events. In addition to any other goto evils, exceptions also have unnecessary overhead compared to alternative, and superior , methods, such as a while loop.

7.5.2 Throwing an Exception

Example 7-6 is part of a larger user registration system. It tries to insert a new user into the database, but throws an exception if there's already someone in the table with the same username.

Example 7-6. Inserting new users into a member database
 // Add user to database, throw exception if duplicate entry function addUser($db, $user) {     $db->query("INSERT INTO users                      VALUES ('${user[0]}', '${user[1]}');");     // Error code 19 means INSERT violates UNIQUEness constraint     if ($db->lastError( ) =  = 19) {          throw new Exception("${user[0]} already in database.", 19);     } } // First element is username, second is password $user = array('rasmus', 'z.8cMpdFbNAPw'); try {     $db = new SQLiteDatabase('address-book.db');     addUser($db, $user); } catch (Exception $error) {     print "Message: ".$error->getMessage( )."\n";     print "Error Code: ".$error->getCode( )."\n"; } 

When there's a username collision, you get:

  Message: rasmus already in database.   Error Code: 19  

Assume the user table's first field was declared UNIQUE during table creation. Therefore, when there's already a rasmus user in the database, trying to insert a second rasmus violates this constraint. This causes SQLite's lastError( ) method to return 19 .

While SQLite's constructor throws exceptions, the query( ) method does not. Therefore, you must manually trigger the exception by creating a new Exception object and passing a message and error code.

The Exception class stores the constructor's first and second parameters in getMessage( ) and getCode( ) . The first parameter, the message, is a string; the second, the error code, must be an integer. Trying to pass a non-number as the second argument causes PHP to complain with a fatal error.

Use the throw keyword to throw an Exception object. In this example, the object is instantiated and thrown in one line, but these operations can occur separately.

The exception isn't caught inside addUser( ) , so it bubbles up a level. However, since the call to addUser( ) is wrapped inside a try/catch block, it's caught and handled there. This is a perfect example of placing the dirty job of checking return values inside a function, thus freeing up the core application logic to use exceptions.

In Example 7-6, the exception handling is minimal. All it does is print out two lines of debugging information. In a full system, you should make the exception trigger a page that requests that the person select a different username. This wasn't demonstrated here, but it's a nicer way to handle problems than just dying.

7.5.3 Writing and Catching Custom Exceptions

There are two good reasons to define custom exception classes: to customize your processing of different errors and to embed additional information inside your exception objects.

7.5.3.1 Handling multiple exception types

Besides throwing the default Exception class, you can create and throw custom exception objects by extending Exception . For instance, Example 7-7 throws a duplicateUsernameException exception.

Example 7-7. Catching a custom exception
 class duplicateUsernameException extends Exception {  }; // Add user to database, throw exception if duplicate entry function addUser($db, $user) {     $db->query("INSERT INTO users                      VALUES ('${user[0]}', '${user[1]}');");     // Error code 19 means INSERT violates UNIQUEness constraint     if ($db->lastError( ) =  = 19) {          throw new duplicateUsernameException(                     "${user[0]} already in database.", 19);     } } // First element is username, second is password $user = array('rasmus', 'z.8cMpdFbNAPw'); try {     $db = new SQLiteDatabase('address-book.db');     addUser($db, $user); } catch (duplicateUsernameException $error) {     print "Message: ".$error->getMessage( )."\n";     print "Error Code: ".$error->getCode( )."\n"; } 

The methods and properties of duplicateUsernameException are identical to its parent, but by subclassing Exception you can catch this error separately and handle it accordingly . This is a good reason to subclass Exception .

To handle the different types of exceptions, define multiple catch blocks. When PHP detects a thrown exception, it works its way down the list of catch es. For each block, PHP compares the exception type against the class name using an instanceof check. PHP uses the first matching block.

Therefore, you must catch a duplicateUsernameException before an Exception . Since duplicateUsernameException is a subclass of Exception , switching the order results in all exceptions being caught in the first block.

For instance, Example 7-8 continues to throw duplicateUsernameException as before, but also remembers that SQLite throws an SQLiteException from its constructor.

Example 7-8. Catching multiple exceptions
 class duplicateUsernameException extends Exception {  }; // Add user to database, throw exception if duplicate entry function addUser($db, $user) {     $db->query("INSERT INTO users                      VALUES ('${user[0]}', '${user[1]}');");     // Error code 19 means INSERT violates UNIQUEness constraint     // Throw duplicateUsernameException     if ($db->lastError( ) =  = 19) {          throw new duplicateUsernameException(                     "${user[0]} already in database.", 19);     } } // First element is username, second is password $user = array('rasmus', 'z.8cMpdFbNAPw'); try {     $db = new SQLiteDatabase('address-book.db');     addUser($db, $user); } catch (duplicateUsernameException $error) {     // This function not implemented     requestDifferentUsername($user); } catch (SQLiteException $error) {     print "Message: ".$error->getMessage( )."\n";     print "Error Code: ".$error->getCode( )."\n"; } 

In this example, a duplicateUsernameException error is thrown if the username is already in the database. Since that's only a minor error, call requestDifferentUsername( ) to recover gracefully.

A failure in the constructor is more severe. Since that's of type SQLiteException , it's trapped in a separate catch block, where you can handle it accordingly.

Without subclassing Exception , you need to place a giant switch inside catch and check the type of each error. This is ugly and doesn't scale well. Additionally, one pattern of object-oriented programming is replacing the switch statements that check an object's type with different subclassed objects.

7.5.3.2 Embedding custom exception data

Another reason to subclass Exception is to add custom methods:

 class duplicateUsernameException extends Exception {     private $username;     public function _ _construct($username) {         parent::_ _construct( );         $this->username = $username;     }     public function getUsername( ) {         return $this->username;     }     public function getAlternativeNames( ) {         // return an array of similar names, like "rasmus34"     } } 

The getAlternativeNames( ) method returns an array of similar names that the application can pass on to the person trying to sign up. You can integrate this method into your code:

 // First element is username, second is password $user = array('rasmus', 'z.8cMpdFbNAPw'); try {     $db = new SQLiteDatabase('address-book.db');     addUser($db, $user); } catch (duplicateUsernameException $error) {     requestDifferentUsername($error); } catch (Exception $error) {     print "Message: ".$error->getMessage( )."\n";     print "Error Code: ".$error->getCode( )."\n"; } 

Now the requestDifferentUsername( ) function is passed an object it can use to discover both the original username and a list of valid alternatives.

 <  Day Day Up  >  


Upgrading to PHP 5
Upgrading to PHP 5
ISBN: 0596006365
EAN: 2147483647
Year: 2004
Pages: 144

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