Debugging in PHP: Inspecting Program Data

Once you clear the parse error hurdle, you still may have some work to do before you reach the finish line. A program can be syntactically correct but logically flawed. Just as the sentence “The tugboat chewed apoplectically with six subtle buffaloes” is gram­matically correct but meaningless nonsense, you can write a program that the PHP engine doesn’t find any problems with but that doesn’t do what you expect.

Finding and fixing parts of a programs that don’t behave as you expect is a big part of programming. The specifics of how you’d diagnose and explore particular situations vary greatly depending on what you’re trying to fix. This section shows you two tech­niques for investigating what’s going on in your PHP program. The first, adding debugging output, is easy, but requires modifying your program and may not be suit­able for a production environment where regular users can also see the output. The second, using a debugger, requires more work to set up properly, but gives you more runtime flexibility as to how you inspect the running program.

1. Adding Debug Output

If your program is acting funny, add some checkpoints that display the values of vari­ables. That way, you can see where the program’s behavior diverges from your expect­ations. Example 12-3 shows a program that incorrectly attempts to calculate the total cost of a few items.

Example 12-3. A broken program

$prices = array(5.95, 3.00, 12.50);

$totat_price = 0;

$tax_rate = 1.08; // 8% tax

foreach ($prices as $price) {

$totat_price = $price * $tax_rate;

}

printf(‘Totat price (with tax): $%.2f’, $totat_price);

Example 12-3 doesn’t do the right thing. It prints:

Total price (with tax): $13.50

The total price of the items should be at least $20. What’s wrong with Example 12-3? One way you can try to find out is to insert a line in the foreach() loop that prints the value of $total_price before and after it changes. That should provide some insight into why the math is wrong. Example 12-4 annotates Example 12-3 with some diagnostic print statements.

Example 12-4. A broken program with debugging output

$prices = array(5.95, 3.00, 12.50);

$total_price = 0;

$tax_rate = 1.08; // 8% tax

foreach ($prices as $price) {

print “[before: $total_price]”;

$total_price = $price * $tax_rate;

print “[after: $total_price]”;

}

printf(‘Total price (with tax): $%.2f’, $total_price);

Example 12-4 prints:

[before: 0][after: 6.426][before: 6.426][after: 3.24][before: 3.24]

[after: 13.5]Total price (with tax): $13.50

From analyzing the debugging output from Example 12-4, you can see that $total_price isn’t increasing on each trip through the foreach() loop. Scrutinizing the code further leads you to the conclusion that the line:

$total_price = $price * $tax_rate;

should be:

$total_price += $price * $tax_rate;

Instead of the assignment operator (=), the code needs the increment-and-assign operator (+=).

Example 12-5. Printing all submitted form parameters with var_dump()

print ‘<pre>’;

var_dump($_POST);

print ‘</pre>’;

To include an array in debugging output, use var_dump(). It prints all the elements in an array. Surround the output of var_dump() with HTML <pre> and </pre> tags to have it nicely formatted in your web browser. Example 12-5 prints the contents of all submitted form parameters with var_dump().

Debugging messages are informative but can be confusing or disruptive when mixed in with the regular page output. To send debugging messages to the web server error log instead of the web browser, use the error_log() function instead of print. Example 12-6 shows the program from Example 12-4 but uses error_log() to send the diagnostic messages to the web server error log.

Example 12-6. A broken program with error log debugging output

$prices = array(5.95, 3.00, 12.50);

$total_price = 0;

$tax_rate = 1.08; // 8% tax

foreach ($prices as $price) {

error_log(“[before: $total_price]”);

$total_price = $price * $tax_rate;

error_log(“[after: $total_price]”);

}

printf(‘Total price (with tax): $%.2f’, $total_price);

Example 12-6 prints just the total price line:

Total price (with tax): $13.50

However, it sends lines to the web server error log that look like this:

[before: 0]

[after: 6.426]

[before: 6.426]

[after: 3.24]

[before: 3.24]

[after: 13.5]

The exact location of your web server error log varies based on how your web server is configured. If you’re using Apache, the error log location is specified by the ErrorLog Apache configuration setting.

Because the var_dump() function itself prints information, you need to do a little fancy footwork to send its output to the error log, similar to the output buffering functionality discussed at the end of “Why setcookie() and session_start() Want to Be at the Top of the Page” on page 226. You surround the call to var_dump() with func­tions that temporarily suspend output, as shown in Example 12-7.

Example 12-7. Sending all submitted form parameters to the error log with var_dump()

// Capture output instead of printing it

ob_start();

// Call var_dump() as usual

var_dump($_POST);

// Store in $output the output generated since calling ob_start()

$output = ob_get_contents();

// Go back to regular printing of output

ob_end_clean();

// Send $output to the error log

error_log($output);

The ob_start(), ob_get_contents(), and ob_end_clean() functions in Example 12-7 manipulate how the PHP engine generates output. The ob_start() function tells the engine, “Don’t print anything from now on. Just accumulate any­thing you would print in an internal buffer” When var_dump() is called, the engine is under the spell of ob_start(), so the output goes into that internal buffer. The ob_get_contents() function returns the contents of the internal buffer. Since var_dump() is the only thing that generated output since ob_start() was called, this puts the output of var_dump() into $output. The ob_end_clean() function undoes the work of ob_start(): it tells the PHP engine to go back to its regular behavior with regard to printing. Finally, error_log() sends $output (which holds what var_dump() “printed”) to the web server error log.

2. Using a Debugger

The printing and logging approach described in the previous section is easy to use. But because it requires modifying your program, you can’t use it in a production environment where regular users might see the debugging output. Also, you need to decide what information you want to print or log before you start running your pro­gram. If you haven’t added any code to print a value you’re interested in, you have to modify your program again and rerun it.

Examining your program with a debugger solves these problems. A debugger lets you inspect your program while it is running so you can see the values of variables and which functions call which other functions. It doesn’t require any changes to your program, but it does require some separate setup.

There are a few debuggers that work with PHP, and many of the editors listed in Table 12-1 integrate well with a debugger to allow you to inspect a running PHP pro­gram from within your editor. This section shows program inspection with the phpdbg debugger, which comes with PHP.

To start a debugging session with phpdbg, run the phpdbg program with a -e argu­ment indicating what program you want to debug:

phpdbg -e broken.php

phpdbg responds with:

Welcome to phpdbg, the interactive PHP debugger, V0.4.0]

To get help using phpdbg type “help” and press enter

[Please report bugs to <http://github.com/krakjoe/phpdbg/issues>]

[Successful compilation of broken.php]

This means that phpdbg has read broken.php, has digested the commands in it, and is ready to run it for you. First, we’re going to set a breakpoint. This tells phpdbg to pause whenever it reaches a certain place in the program. When phpdbg pauses, we can inspect the program’s innards. Line 7 is the line where $total_price gets its value assigned within the loop, so let’s break there:

prompt> break 7

The prompt> part is not something to type. phpdbg prints that on its own as a prompt telling you it is ready for a command. The break 7 command tells phpdbg to pause execution when it reaches line 7 of the program. phpdbg responds with:

[Breakpoint #0 added at broken.php:7]

We’re ready to go, so tell phpdbg to start running the program:

prompt> run

It starts from the beginning, running each line of the program until it gets to the breakpoint at line 7. At that point, phpdbg says:

[Breakpoint #0 at broken.php:7, hits: 1]

>00007:   $total_price = $price * $tax_rate;

00008: }

00009:

Now we can add a watch point for $total_price. This tells phpdbg to pause program execution each time the value of $total_price changes. This is exactly what we need to diagnose our problem, since it’s the value of $total_price that’s not getting set to what we expect. The watch command adds a watch point:

prompt> watch $total_price

phpdbg responds with:

[Set watchpoint on $total_price]

Now that we have our watch point, we don’t need the breakpoint on line 7 any more. The break del command deletes a breakpoint:

prompt> break del 0

This tells phpdbg to remove the first breakpoint we set (like PHP with array elements, phpdbg starts numbering things with 0, not 1). phpdbg acknowledges the breakpoint deletion with:

[Deleted breakpoint #0]

We are all set to continue running the program and have it pause whenever the value of $total_price changes. The continue command tells phpdbg to keep going:

prompt> continue

phpdbg starts running the program. The first commands that now get executed are the ones in line 7, which change the value of $total_price. So right away program execution is paused, and phpdbg says:

[Breaking on watchpoint $total_price]

Old value: 0 New value: 6.426

>00007:   $total_price = $price * $tax_rate;

00008: }

00009:

This is useful—we see that the code is changing $total_price from 0 to 6.426. Let’s see what happens next. The continue command tells phpdbg to get things going again:

prompt> continue

And then the program stops again:

[Breaking on watchpoint $total_price]

Old value: 6.426 New value: 3.24

>00007:   $total_price = $price * $tax_rate;

00008: }

00009:

Back again on line 7 in the loop, $total_price goes from 6.426 to 3.24. That defi­nitely doesn’t look right—$total_price should be increasing! Let’s keep going:

prompt> continue

One last time, the value of $total_price gets changed:

[Breaking on watchpoint $total_price]

Old value: 3.24 New value: 13.5

>00007:   $total_price = $price * $tax_rate;

00008: }

00009:

This time it increases to 13.5. And a final continue to finish out the program:

prompt> continue

phpdbg keeps running the program, and we get the actual program output:

Total price (with tax): $13.50

[$total_price was removed, removing watchpoint]

[Script ended normally]

The second time phpdbg pauses at the watch point, it is clear that there is a problem with how the value of $total_price is being calculated. This is the same conclusion that the debugging output introduced in the previous section shows.

The specific syntax to type (or places to click in a GUI) may vary with a different debugger or IDE, but the basic idea is the same: the debugger runs your program with special oversight. You get to pause your program execution in the places of your choosing and inspect the program’s guts when it pauses.

Source: Sklar David (2016), Learning PHP: A Gentle Introduction to the Web’s Most Popular Language, O’Reilly Media; 1st edition.

Leave a Reply

Your email address will not be published. Required fields are marked *