Debugging Your PHP Scripts


Tracking down and removing bugs in applications is a fact of life when writing computer programs, regardless of language. Unlike many development platforms, debugging Web applications offers a unique challenge that at times can be difficult. As you will see, however, many different tools (both commercial and open source) and a number of techniques have been developed that will be infinitely useful to you.

When you are writing programs, regardless of language, bugs can be classified into two different categories: syntax and logical bugs. Syntax bugs are very easy to identify because they always relate to errors you have made in the actual writing of the program itself. These errors can be forgetting a semicolon or brace, simple typos, or other syntax-related errors. Because most syntactic bugs will prevent your application from running at all, they are usually obvious to spot. On the other hand, the second classification of bugs, logical bugs, can be much more elusive. Logical bugs are not always immediately obvious, because your application may function as intended 90 percent of the time, breaking only under unique circumstances. As you will see, most of my discussion of debugging will be focused on techniques and tools for finding and fixing logical bugs.

Syntax-Related Bugs

The easiest bugs to find in your application are syntax-related bugs; they will always cause some sort of error and more than likely will halt your script entirely. Most syntax-related bugs materialize with an error message resembling the following:

 PHP Parse error: parse error, unexpected ??? in <filename> on line <line_number> 

Where ??? can be a number of different values (see the note that follows), <filename> is the name of the file where the error occurred, and <line_number> is the line number where the error was detected.

NOTE

In parse errors, the ??? represents a scanner token. If you are curious as to what types of values may occur, see the PHP documentation for the tokenizer extension, which provides a complete list of all valid PHP tokens.


It is important to note that when you are dealing with errors such as this, PHP can tell you only where the error was detected, not necessarily where it actually occurred. Thus, when dealing with syntax errors, it is important to realize the line number that PHP reports as causing the error may be inaccurate. A common example of this is if a control block is started using the { character, but is not closed. In a situation such as this, PHP will report a Parse error occurring on a line number that does not exist (one past the end of the file). Thus when attempting to track down syntax-related errors, always check up your code starting from the line where PHP first detected the error.

Logical Bugs

Logical bugs represent what most programmers classify as "bugs" that occur when your application runs, but doesn't run correctly. Unfortunately, there is no way to teach a single technique that will ensure that your code will be free from these bugs. Rather, a combination of good practices, useful techniques, knowledge of the code in question, and experience are needed to properly address logical bugs. Because this chapter (or this book for that matter) can't provide you with knowledge of an arbitrary piece of code or experience, we'll have to settle with a discussion of the best practices and useful debugging techniques.

Preventing Bugs

As is the case anytime something is being created, the best way to encourage a positive outcome is to appropriately plan that which you are creating. Thus, the first step in creating any application is to have a reasonable idea of how exactly you plan to create it. This planning can be accomplished in any way you see fitfrom using UML, to creating a detailed design document, to just having a decent idea in your head. The important piece here is not how the plan is fashioned, but that the plan has indeed been fashioned to some degree. What is the goal of the script you are writing? Do you know what exactly is involved in creating this script? Do you have at the very least a rough idea of how you will implement this script? These are all questions that any good developer answers before writing a single line of code. It may seem like a bit of a waste of time, but the time spent early on can save huge headaches and time debugging later.

After you have a plan, another solid debugging technique again has nothing to do with the code itself. Rather, it involves establishing a programming style that will be used when the code is written. How will you name your functions and variables? How many spaces will you use for your indentation for each layer of code? Although these questions are trivial and unnecessary for smaller scripts, large scripts quickly become unmanageable and therefore bug-prone without a coding standard.

Last but not least, especially for large projects, is documentation. As scripts become larger and larger, it is easy to forget exactly what a particular function is used for or when it is called. Don't be afraid to comment your code! On the other side of the coin, however, don't comment too much, either. Although everyone has an individual commenting style (as well as some formal styles used with documentation-generation systems such as PHPDoc), comments shouldn't be more than perhaps a function description and brief in-line comments when necessary. By getting into the habit of commenting your code when the application logic is not immediately obvious, you accomplish two goalsnot only do you make your application easier to understand, but it also forces you to think about your code, allowing you to sometimes catch oversights before they become a real issue.

Simple Script Tracing

Unfortunately, no matter how well thought out a particular script or application is, no matter how talented or experienced the programmer, there will always be bugs in any significant amount of code. Tracking down these logic-related bugs, as I've already mentioned, can be an extremely difficult and time-consuming task. Although experience is by far the best tool to finding these bugs, PHP provides a few things that can make your life as a debugger easier.

One of the most important things to remember when debugging your scripts is this: To fix a bug, first you must understand the bug. Without properly understanding exactly why a particular piece of code is not functioning properly, any attempts made to fix it could very well result in the debugging version of wack-a-mole, where fixing one bug causes another to pop up. To understand why a particular bug is in your code, first you'll need to see exactly what your application is doing. This can become a problem for a language such as PHP, because your script is often running on a server that could be halfway around the world, thus making the standard debugging tools useless.

Although many common debugging tools are not useful when you're working with PHP, some functions and techniques can be used to ease your debugging life. The first of these is any of the standard output functions such as echo or printf. The technique is simpleif you are curious about the flow of your application logic or the value of a particular variable, display it using a statement such as echo (see Listing 10.1):

Listing 10.1. The Poor Man's Application Trace
 <?php      $foo = rand(1,10);      echo "The value of \$foo is: $foo<BR />";      if($foo > 5) {           echo "Hello, World!<BR />";      } ?> 

Listing 10.1 provides a simple example of what is affectionately called the "poor man's application trace." Obviously, using a debugging method such as this has some significant drawbacks that must be addressed. For starters, it can quickly become incredibly annoying to write echo statement after echo statement (and then remove each later) to follow the flow of your script. One solution that solves at least half of this problem is to wrap every debugging message in a conditional operator and turn the messages on and off based on a constant as shown in Listing 10.2:

Listing 10.2. A Slightly Improved Poor Man's Application Trace
 <?php      define('DEBUG', true);      $foo = rand(1,10);      debug("The value of \$foo is: $foo<BR />");      if($foo > 5) {           echo "Hello, World!<BR />";      }      function debug($dbgmsg) {           if(DEBUG) {                echo $dbgmsg;           }      } ?> 

In Listing 10.2 we have improved slightly on the concept of the poor man's application trace by creating a debug() function that handles the actual output of any debugging messages by first checking to see if the DEBUG constant is true. Using this method, we have a fairly reasonable method of following your script logic without having it become a maintenance nightmare. We could improve on this technique even further if we wanted by logging debugging messages to a file, displaying them in a pop-up window using some JavaScript magic, or both, as shown in Listing 10.3:

Listing 10.3. An Even Better Poor Man's Application Trace
 <?php      define('DEBUG', true);      $foo = rand(1,10);      debug("The value of \$foo is: $foo<BR />");      if($foo > 5) {           echo "Hello, World!<BR />";      }      function debug($dbgmsg) {           if(!DEBUG) return;           error_log($dbgmsg);           $dbgmsg = addslashes(htmlentities($dbgmsg));           $dbgmsg = nl2br($dbgmsg);           $dbgmsg = str_replace("\n", "", $dbgmsg);           $dbgmsg = str_replace("\r", "", $dbgmsg);      ?>      <SCRIPT LANGUAGE="JavaScript" TYPE="text/javascript"     >           <!--           debug_console("<PRE><?php echo $dbgmsg; ?></PRE>");           //-->      </SCRIPT>      <?php      } ?> 

NOTE

Although it's not immediately obvious, the reason I am doing so many manipulations to the debugging message in this example is to avoid problems with JavaScript when dealing with multiple-line strings. In JavaScript, all strings must be represented on a single line, and therefore all line breaks must be removed.


In Listing 10.3, we have yet again improved on our tried and true debugging technique by logging debugging information using PHP's error_log() function and displaying it within its own pop-up window using some JavaScript code. For Listing 10.3 to work, however, you must have the debug_console() JavaScript function defined somewhere in your HTML output. Following is the debug_console() function code snippet I used for this example:

 <SCRIPT LANGUAGE="JavaScript"> <!-- function debug_console(content) {             top.consoleRef=window.open('','myconsole',                                        'width=640,height=350'                                        +',menubar=0'                                        +',toolbar=0'                                        +',status=0'                                        +',scrollbars=1'                                        +',resizable=1')             top.consoleRef.document.writeln(                       '<html><head><title>My Debugging Console</title></head>'                       +'<body bgcolor=white onLoad="self.focus()">'                       +content                       +'</body></html>'         ) //--> </SCRIPT> 

Using Assertions in PHP

Another potentially useful technique for debugging PHP applications, assertions, is also a common technique found in other languages. Assertions are statements that you define in your scripts that are assumed to always be either true or false. Although they are not designed to be used in the course of your normal script logic, assertions can be quite useful for performing sanity checks to ensure variables within your application are at least realistic values.

The reason assertions are particularly useful for this task in a development scenario is their ease of configuration for different situations. For instance, assertions can be turned on and off using a single function call, among other useful capabilities.

In practice, assertions are used in PHP through two separate functions, assert() and assert_options(), which define assertions and the behavior of those assertions, respectively. The syntax for the assert() function is as follows:

 assert($assertion); 

$assertion is the assertion to evaluate. This value can either be a string that will be evaluated as PHP code or a Boolean expression. In either case, the result should be written in such a way that the result evaluates to a Boolean false.

NOTE

In general, the $assertion parameter should be represented as a string. Beyond being more efficient (because it will not be evaluated unless assertions are enabled), it also provides more information when the assertion fails, as you will soon see.


Because the best way to understand assertions is to see them in action, let's take a look at a small PHP script example shown in Listing 10.4:

Listing 10.4. Using the assert() Function
 <?php     function add_odd_numbers($x, $y) {         assert('!(($x % 2) && ($y % 2))');         return ($x + $y);     }     $answer_one = add_odd_numbers(3, 5);     $answer_two = add_odd_numbers(2, 4);     echo "3 + 5 = $answer_one\n";     echo "2 + 4 = $answer_two\n"; ?> 

In this trivial example a function add_odd_numbers() has been defined, which accepts two parameters, $x and $y, representing the numbers to add. Within this function I have placed an assertion and provided it the string '!(($x % 2) && ($y % 2))' as a parameter. This function is then used in the remainder of the script in both an appropriate and inappropriate way.

When this script is executed, it will produce the following output:

 Warning: assert(): Assertion "!(($x % 2) && ($y % 2))" failed in assert.php on line 4 3 + 5 = 8 2 + 4 = 6 

As you can see from this output, by using assert() it is clear that something that was assumed to be true (the parameters being passed are odd numbers) turned out for some reason to be not true. In this case, as is the default behavior, the result is a runtime warning:

 Warning: assert(): Assertion "!(($x % 2) && ($y % 2))" failed in assert.php on line 4 

Immediately, this warning identifies not only the location within the source tree of the failed assertion, but also provides the assertion statement that failed.

Beyond this default behavior, assertions in PHP can also trigger a number of other events. All these additional behaviors are controlled through the use of the assert_options() function, whose syntax is as follows:

 assert_options($option [, $value]); 

$option is one of the constants found in Table 10.1, and the optional parameter $value is the value to set that option to. When executed, the assert_options() function will return the current value for the provided option and, if provided, set that option to the new value. The possible options to configure when working with assertions are as follows:

Table 10.1. Assertion Options

Constant Name

Default

Description

ASSERT_ACTIVE

true

Are assertions enabled?

ASSERT_WARNING

true

Should assertions cause standard PHP warnings?

ASSERT_BAIL

false

Should failed assertions cause the script to halt?

ASSERT_QUIET_EVAL

false

If an error occurs valuating an assertion when passed a string, should it report an error?

ASSERT_CALLBACK

NULL

The name of the function to call if an assertion fails.


As you can see, assertion in PHP are a fairly flexible construct and have many uses in the development and debugging of PHP applications. One of the more interesting uses of assertions is when a callback function is provided. By registering a callback function, you then have the capability to process failed assertions on your own, making things such as automated test suites much easier. When using an assertion callback function, assert() will call it and pass it three parameters: the filename where the assertion failed, the line number of the failed assertion, and if available, the assertion code that failed. An example of using a callback function with assertions can be found in Listing 10.5:

Listing 10.5. Using Assertion Callbacks
 <?php     assert_options(ASSERT_CALLBACK, "assert_failure");     assert_options(ASSERT_WARNING, false);     function assert_failure($filename, $line_num, $asserted_code) {         $code = (empty($asserted_code)) ? "Unknown code" : $asserted_code;         echo "The assertion '$asserted_code' failed in '$filename' " .              "(line: $line_num)\n";     }     function integer_divide($x, $y) {         assert('!(is_long($x) && is_long($y))');         return (int)($x / $y);     }     $answer = integer_divide(10, 6);     echo "10 / 6 = $answer\n"; ?> 



PHP 5 Unleashed
PHP 5 Unleashed
ISBN: 067232511X
EAN: 2147483647
Year: 2004
Pages: 257

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