7.8 Debugging Functions

 <  Day Day Up  >  

Debugging is the bane of every programmer. When you're trying to untangle why a variable isn't set correctly inside a function ”which was itself called from another function, which was included from an include file, which was wrapped inside a class ”it can be maddening to track down where in the chain of events everything started to go wrong.

PHP 5 includes a set of functions that ease your pain. The debug_backtrace( ) and debug_print_backtrace( ) functions return and print, respectively, an assortment of information about the current function and every previous function in the call stack.

7.8.1 Integrating Debugging into Your Code

One good strategy for deploying debug_print_backtrace( ) is to define( ) a DEBUG constant whose value determines whether your code should run in debugging mode. Example 7-9 is a modified version of Example 7-8 with integrated debugging scaffolding.

Example 7-9. Integrating backtrace debugging
 // Enable debug mode; // Can be placed in a file that's added using the // auto_prepend_file configuration directive define('DEBUG', true); 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);     }     // Another error, like a malformed SQL statement     if (DEBUG && $db->lastError( )) {          debug_print_backtrace( );     } } 

At the top of the script, DEBUG is defined to true . This enables debug mode. Using a constant instead of a variable prevents a script from modifying the value. Another advantage is that constants have global scope, which means you can use them inside of functions and classes without declaring them global . (Forgetting to reference the global $debug variable results in a bug in the bug-catching code, which is quadruply frustrating.)

A good place to define DEBUG is in a file that's automatically included at the top of every file using the auto_prepend_file configuration directive. This ensures that you won't forget to set a value for the constant.

Underneath the check to throw a duplicateUsernameException inside addUser( ) is:

 if (DEBUG && $db->lastError( )) {          debug_print_backtrace( );     } 

This block causes PHP to print out debugging information and helps you locate the error.

The debug_print_backtrace( ) function is always available, but if you're using exceptions, you can also call the getTraceAsString( ) method, which returns equivalent results:

 // Another error, like a malformed SQL statement     // Throw Exception     if ($db->lastError( )) {          $e = new Exception(                     sqlite_error_string($db->lastError( )),                      $db->lastError( ));         if (DEBUG) { print $e->getTraceAsString( ) . "\n"; }         throw $e;     } 

Example 7-10 works correctly when you test it, but triggers an exception for some (but not all) people trying to sign up for your site.

Example 7-10. Inserting new users incorrectly
 // Can be placed in a file that's added using // auto_prepend_file configuration directive define('DEBUG', true); 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);     }     // Another error, like a malformed SQL statement     if (DEBUG && $db->lastError( )) {          print sqlite_error_string($db->lastError( ));     } } $user = array($_GET['username'], $_GET['password']); try {     $db = new SQLiteDatabase('address-book.db');     addUser($db, $user); } catch (duplicateUsernameException $error) {     requestDifferentUsername($user); } catch (SQLiteException $error) {     print "Message: ".$error->getMessage( )."\n";     print "Error Code: ".$error->getCode( )."\n"; } 

The SQLite error message for this bug, as reported by sqlite_error_string($db->lastError( ) , is:

  SQL logic error or missing database  

The SQLite error message doesn't provide you with much to go on. You know the database exists because you see it on the filesystem. You also know it's not a permissions problem because you're accessing the database in the same manner as everyone else. Besides, it works for most people, so it's not like the code is completely broken.

Eventually, you find a case that triggers debug_print_backtrace( ) . Here's the output from the function:

  #0  addUser(SQLiteDatabase Object ( ), Array ([0] => rasmus,[1] => sec'ret%))  called at [/www/www.example.com/sqlite.php:31]  

The username looks okay, but maybe there's something wrong with the password. Aha! It contains a single quotation mark ('), and you're not calling sqlite_escape_string( ) on the input. In addition to a security vulnerability, this causes an SQL parse error because your INSERT statement terminates early.

The fix is simple. You must escape the values before passing them to SQLite, as shown in Example 7-11.

Example 7-11. Inserting new users correctly
 // Add user to database, throw exception if duplicate entry function addUser($db, $user) {     // Escape input parameters     foreach ($user as &$value) {         $value = sqlite_escape_string($value);     }     $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);     }     // Another error, like a malformed SQL statement     if (DEBUG && $db->lastError( )) {          debug_print_backtrace( );     } } 

The foreach loop cycles through the elements of $user using a new PHP 5 feature: iteration by reference. Placing an ampersand ( & ) before $value makes foreach provide you with a reference to array elements. This allows you to reassign the output of sqlite_escape_string( ) to $value instead of $user[$key] .

7.8.2 Redirecting Output to a File

Use PHP's output-buffering functions to redirect the results of debug_print_backtrace( ) to the error log or a file:

 // Another error, like a malformed SQL statement     if (DEBUG && $db->lastError( )) {          $ob = ob_start( );         debug_print_backtrace( );         error_log(ob_get_clean( ));     } 

This creates an output buffer, $ob , and places the contents of debug_print_backtrace( ) inside it. The buffer is then flushed to the error log using ob_get_clean( ) .

 <  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