While you can use PHP to create command-line scripts for such tasks as systems administration and traditional data processing, the language predominantly powers Web applications. In this employment, each PHP application resides on a server and is invoked through a proxy, such as Apache, to process an incoming request. With each request, a typical PHP Web application runs very briefly, yielding a Web page or an XML data structure.
Given the brevity of execution and the tiered construction of Web applications — there's the client, the network, the HTTP server, the application code, and the underlying database — it can be difficult to isolate bugs in PHP code. Even if you assume that all the tiers except the PHP code work flawlessly, tracking down an error in PHP code can be frustrating, especially (and perhaps ironically) as an application leverages more and more classes.
The PHP statement echo and the functions var_dump(), debug_zval_dump(), and print_r() are common and popular debugging aids that can help solve
a variety of issues. However, these statements — and even more robust
instrumentation, such as the PEAR Log package
— are forensic tools, producing evidence that you must analyze a priori and out of context.
To an extent, debugging through deduction is a brute-force approach. You collect data and sift through it, trying to deduce what's happened. If you lack vital information, you must reinstrument your code, repeat your steps, and restart your investigation. A far more efficient strategy is to probe the application while it's running. You can catalog request parameters, sift through the procedure call stack, and query any variable or object you'd like. You can temporarily interrupt the application and be alerted when a variable changes value. In some cases, you can actually affect variables interactively to ask "What if?" questions.
A special application called a debugger facilitates such "live," or interactive, inspection. A debugger potentially launches and attaches to a process to control it and to inspect its memory. Or, in the case of interpreted languages, the debugger may interpret code directly. A typical modern graphical debugger can index and browse code, readily display complex data structures in human-readable form, and simultaneously display program state, such as the call stack, intermediate output, and the values of all variables. For example, it's common for a debugger to catalog and picture a class' properties and methods.
In this article and the next, I introduce tools sure to simplify your PHP debugging. Next time, I'll focus on interactive debugging and the Zend Debugger — a robust debugger especially suited for PHP — and explore the many features it offers. (The Zend Debugger is a commercial product, provided as part of the Zend PHP Integrated Development Environment (IDE).) I'll also look at an open source PHP debugger, in case you prefer to spend your money on beer, rather than code. This article, though, focuses on collecting better forensics.
When code goes awry, failing to produce some expected result or crashing outright, you want answers to the Four w's: where, what, why, and when:
- The "where" is the file and line number where the application was last seen breathing.
- The "what" is the offending code — the suspect, so to speak.
- The "why" is the nature of the error. Perhaps it's a logic error or an error caused by interacting with the operating system or both.
- And "when" is the context in which the error occurred. What happened immediately before the program died? As in any crime, clues can yield the culprit if you can collect enough clues.
One forensic tool, Xdebug (used in a previous article to profile PHP application performance), as its name implies, provides several features to illuminate program state and is a valuable investigative tool to add to your repertoire (see Resources). When installed, Xdebug intervenes to prevent infinite (well, seemingly so) recursions, amends error messages with stack traces and function traces, and monitors memory allocation, among other features. Xdebug also includes a set of functions that you can add to your code to yield runtime diagnostics.
For example, the code below uses a handful of xdebug_...() procedures to instrument the callee() function to print the specific location of the caller,
including the file name, the line number, and the name of the calling function.
Listing 1. Procedures to instrument the callee() function
<?php
function callee( $a ) {
echo sprintf("callee() called @ %s: %s from %s",
xdebug_call_file(),
xdebug_call_line(),
xdebug_call_function()
);
}
$result = callee( "arg" );
?>
|
This code produces:
callee() called @ /var/www/catalog/xd.php: 10 from {main}
|
Xdebug readily builds from source on UNIX®-like operating systems, including Mac OS X. If you use PHP on Microsoft® Windows®, you can download a binary Xdebug module for recent versions of PHP from the Xdebug Web site (see Resources).
Let's build and install Xdebug for Debian "Sarge" Linux® and PHP V4.3.10-19. As of this writing, the latest version of Xdebug is V2.0.0RC4, released 17 May 2007. To proceed, you must have the phpize and php-config utilities, and you must be able to edit your system's php.ini configuration file. (If you do not have the utilities, refer to PHP.net for source code and instructions on how to build PHP from scratch.) To proceed:
- Download the Xdebug tarball (a gzip-compressed .tar archive). The
wgetcommand makes the job easy:$ wget http://www.xdebug.org/files/xdebug-2.0.0RC4.tgz
- Extract the tarball and change to the directory of the source code:
$ tar xzf xdebug-2.0.0RC4.tgz $ cd xdebug-2.0.0RC4
- Run
phpizeto prepare the Xdebug code for your version of PHP:$ phpize Configuring for: PHP Api Version: 20020918 Zend Module Api No: 20020429 Zend Extension Api No: 20021010
The output ofphpizeis a script — aptly named configure — used to tailor the rest of the build process.
- Run the configure script:
$ ./configure checking build system type... i686-pc-linux-gnu checking host system type... i686-pc-linux-gnu checking for gcc... gcc checking for C compiler default output file name... a.out checking whether the C compiler works... yes checking whether we are cross compiling... no checking for suffix of executables... checking for suffix of object files... o ... checking whether stripping libraries is possible... yes appending configuration tag "F77" to libtool configure: creating ./config.status config.status: creating config.h
- Build the Xdebug extension by running
make:$ make /bin/sh /home/strike/tmp/xdebug-2.0.0RC4/libtool --mode=compile gcc -I. -I/home/strike/tmp/xdebug-2.0.0RC4 -DPHP_ATOM_INC -I/home/strike/tmp/xdebug-2.0.0RC4/include -I/home/strike/tmp/xdebug-2.0.0RC4/main -I/home/strike/tmp/xdebug-2.0.0RC4 -I/usr/include/php4 -I/usr/include/php4/main -I/usr/include/php4/Zend -I/usr/include/php4/TSRM -DHAVE_CONFIG_H -g -O0 -c /home/strike/tmp/xdebug-2.0.0RC4/xdebug.c -o xdebug.lo mkdir .libs ... Build complete. (It is safe to ignore warnings about tempnam and tmpnam).
Themakeyields the Xdebug extension, xdebug.so.
- Install the extension:
$ sudo make install Installing shared extensions: /usr/lib/php4/20020429/
Before you continue, use your mouse to select and copy the directory that the last command showed. The path is vital to configuring the extension, which is the final step.
- Open your php.ini file in your favorite text editor, then add the following:
zend_extension = /usr/lib/php4/20020429/xdebug.so xdebug.profiler_enable = Off xdebug.default_enable = On
The first line loads the Xdebug extension; the second disables the profiler feature of Xdebug (just to keep things simple), and the third enables the debug features of the extension.
To verify that the Xdebug extension is installed and enabled, restart your Web server,
then create a simple one-line PHP application with the code <?php phpinfo(); ?>. If you point your browser to the file
— say, http://localhost/phpinfo.php — and scroll down, you should see
something like Figure 1.
Figure 1. Proof that the Xdebug extension is installed and working
Note: If you don't see an Xdebug section in the output of phpinfo(), Xdebug failed to load. Your Apache error logs may list
the reason. Common errors include a wrong path for zend_extension or a conflict with another extension. For instance,
if you want to use XCache and Xdebug, be sure to load XCache first. However, because
Xdebug is intended for use during development and assuming that the path to xdebug.so
is correct, disable other extensions and retry. You can then re-enable the extensions
to do other testing, such as the effectiveness of caching. The Xdebug site has a few
other troubleshooting tips, too.
The directives (in the far left column in the large table in Figure 1) are just some of the parameters you can set to alter the behavior of the Xdebug extension. You set all directives in the php.ini file. Some of the directives configure the debug tools; others tailor the operation of the profiler. Ignoring the latter, let's configure Xdebug with reasonable settings to help debug PHP code.
If your application uses recursion — say, to compute a Fibonacci number
— and your terminal conditions are incorrect, the application can run for
a very long time before it runs out of memory or time. You can set the xdebug.max_nesting_level parameter to limit the depth of recursion.
For example, xdebug.max_nesting_level = 50 limits recursion
to a depth of 50 nested calls before the application is forced to terminate. To
demonstrate, run the following code with Xdebug enabled.
Listing 2. Limiting recursion
<?php
function deep_end( ) {
deep_end();
}
deep_end();
?>
|
The function deep_end() quite literally goes off the deep
end. Xdebug intervenes after 49 function calls and yields Figure 2. (By the way, the
initial invocation of main() to launch the program counts as the first frame.)
Figure 2. Xdebug terminates execution if the call stack exceeds its limit
If your application uses recursion extensively to divide and conquer large problems,
set the depth "deeper" accordingly. Otherwise, set xdebug.max_nesting_level to something nominal to catch runaway
sequences of function calls more quickly.
When an error occurs, you want answers to the four w's. Xdebug can provide all that information — immediately. Here's a batch of helpful settings to start with; you can finetune these at any time.
Listing 3. Errors
xdebug.dump_once = On
xdebug.dump_globals = On
xdebug.dump_undefined = On
xdebug.dump.SERVER = REQUEST_METHOD,REQUEST_URI,HTTP_USER_AGENT
xdebug.dump.REQUEST=*
xdebug.show_exception_trace = On
xdebug.show_local_vars = 1
xdebug.var_display_max_depth = 6
|
The settings xdebug.dump_once, xdebug.dump_globals, xdebug.dump_undefined, and xdebug.dump_SUPERGLOBAL
(where
SUPERGLOBAL
can be COOKIE, FILES, GET, POST, REQUEST, SERVER, or SESSION) control which PHP
superglobals are included in any diagnostic result.
Set xdebug.dump_globals to On to
dump the superglobals named in xdebug.dump_SUPERGLOBAL
settings. For instance,
xdebug.dump_SERVER =
REQUEST_METHOD,REQUEST_URI,HTTP_USER_AGENT prints the PHP superglobals
$_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], and $_SERVER['HTTP_USER_AGENT']. If you want to print all the values in
a superglobal array, use an asterisk (*), as in xdebug.dump_REQUEST=*. If you further set xdebug.dump_undefined to On and a named
superglobal variable isn't set, the variable is still printed with the value undefined.
The line xdebug.show_exception_trace = On forces an
exception trace even if you catch the exception. The line xdebug.show_local_vars = 1 prints all local variables in the
outermost scope of each function call, including variables not yet initialized. And
xdebug.var_display_max_depth = 6 dictates how deeply to dump
a complex variable.
Listing 4 shows all the relevant settings for Xdebug for your php.ini file.
Listing 4. Settings for php.ini file
zend_extension = /usr/lib/php4/20020429/xdebug.so
xdebug.default_enable = On
xdebug.show_exception_trace = On
xdebug.show_local_vars = 1
xdebug.max_nesting_level = 50
xdebug.var_display_max_depth = 6
xdebug.dump_once = On
xdebug.dump_globals = On
xdebug.dump_undefined = On
xdebug.dump.REQUEST = *
xdebug.dump.SERVER = REQUEST_METHOD,REQUEST_URI,HTTP_USER_AGENT
|
Save those settings (or something akin) to your php.ini file, then restart your Web server.
The following example shows what happens when an error occurs. Modify your "off-the-deep-end" code to resemble Listing 5.
Listing 5. Modifying error code
<?php
function deep_end( $count ) {
// add one to the frame count
$count += 1;
if ( $count < 48 ) {
deep_end( $count );
}
else {
trigger_error( "going off the deep end!" );
}
}
// main() is called to start the program,
// so the call stack begins with one frame
deep_end( 1 );
?>
|
If you run this new code, you should see a great deal more information, as shown below.
Figure 3. Dump of superglobals, stack, and local variables at time of error
The text of the message passed to trigger_error is shown at
the top. At the bottom is the list of requested $_SERVER
elements and a list of defined $_REQUEST elements. At the
very bottom is a list of variables in scope #48, which is a
call to deep_end(), according to the manifest. In the call,
$count was integer 48. With this Xdebug configuration in
place, you now have more clues to track down the perpetrator.
Here's one more tip: Xdebug provides an enhanced var_dump()
function that's especially helpful with PHP arrays and classes. For instance, Listing 6
shows a simple (PHP V4) class and instances.
Listing 6. PHP V4 class and instances
<?php
class Person {
var $name;
var $surname;
var $age;
var $children = array();
function Person( $name, $surname, $age, $children = null) {
$this->name = $name;
$this->surname = $surname;
$this->age = $age;
foreach ( $children as $child ) {
$this->children[] = $child;
}
}
}
$boy = new Person( 'Joe', 'Smith', 4 );
$girl = new Person( 'Jane', 'Smith', 6 );
$mom = new Person( 'Mary', 'Smith', 34, array( $boy, $girl ) );
var_dump( $boy, $mom );
?>
|
And Listing 7 shows the output of var_dump().
Listing 7.
var_dump() output
object(person)
var 'name' => string 'Joe' (length=3)
var 'surname' => string 'Smith' (length=5)
var 'age' => int 4
var 'children' =>
array
empty
object(person)
var 'name' => string 'Mary' (length=4)
var 'surname' => string 'Smith' (length=5)
var 'age' => int 34
var 'children' =>
array
0 =>
object(person)
var 'name' => string 'Joe' (length=3)
var 'surname' => string 'Smith' (length=5)
var 'age' => int 4
var 'children' =>
array
empty
1 =>
object(person)
var 'name' => string 'Jane' (length=4)
var 'surname' => string 'Smith' (length=5)
var 'age' => int 6
var 'children' =>
array
empty
|
If you use Xdebug with PHP V5 classes, the dump includes such attributes as public, private, and protected.
Solving a bug — like solving a good murder mystery — often requires construction of a detailed timeline. For example, a memory leak typically doesn't exhibit itself as an errant computation. Instead, operations proceed normally until memory runs out and the application abruptly terminates. If the memory leak is exacerbated by certain requests, failures can be intermittent and hard to predict. A timeline mapping memory usage to time would reveal the severity of the leak. A fine-grain timeline — say, from function to function — would point further to the source of the leak.
Xdebug can provide a detailed timeline as an execution trace. When tracing is enabled, Xdebug logs each function call, including each function's arguments and return value. You can format each log, or trace, to be human-readable or machine-readable. You'd consume the former, while you might write a separate, specific application to analyze the latter.
As with dumps, Xdebug has several php.ini options to customize what is traced. As an example, the following batch of settings produces the most verbose output.
Listing 8. Trace customization
xdebug.trace_format = 0
xdebug.auto_trace = On
xdebug.trace_output_dir = /tmp/traces
xdebug.trace_output_name = trace.%c.%p
xdebug.collect_params = 4
xdebug.collect_includes = On
xdebug.collect_return = On
xdebug.show_mem_delta = On
|
Setting xdebug.auto_trace = 1 enables tracing automatically
prior to execution of any PHP script. Alternatively, you can set xdebug.auto_trace = 0, and use the xdebug_start_trace() and xdebug_stop_trace() functions to enable and disable tracing,
respectively, from your code. However, if xdebug.auto_trace
is 1, you can start your trace before the configured auto_prepend_file is included.
The options xdebug.trace_ouput_dir and xdebug.trace_output_name control where trace output is saved. Here,
all files are persisted in /tmp/traces, and each trace file begins with trace.
followed by the name of the PHP script (%s) and the process
ID (%p). All Xdebug trace files end with the suffix
.xt.
By default, Xdebug displays fields for time, memory usage, function name, and the depth
of the function call. If you set xdebug.trace_format to
0, the output is human-readable. (Set the parameter to 1 for machine format.) Additionally, you can find the growth or
reduction in memory usage if you specify xdebug.show_mem_delta =
1, and you can find the type and values of incoming parameters with xdebug.collect_params = 4. To monitor the value that each function
returns, set xdebug.collect_return = 1.
It's time for another example. Create a /tmp/traces directory, then change its mode to
world-readable and world-writable with mkdir /tmp/traces; chmod
a+rwx /tmp/traces. (If you hesitate to make the traces directory widely available,
make sure that at least the Web server user — typically, www or
nobody
— can write to the directory.) Add the trace settings above
to your php.ini file, restart your Web server, then point your browser to the phpinfo() application again. The entire trace should look something
like Listing 9.
Listing 9. Entire trace
TRACE START [2007-06-06 14:04:55]
0.0003 9440 +9440 -> {main}() /var/www/catalog/t/info.php:0
0.0005 9440 +0 -> phpinfo() /var/www/catalog/t/info.php:1
>=-> TRUE
>=-> 1
0.2351 9208
TRACE END [2007-06-06 14:04:55]
|
Here, main() calls phpinfo(),
which returns TRUE. When main()
exits, it returns 1. Next, point your browser to the "deep
end" or some other PHP application on your system to generate a more substantial trace.
Listing 10 shows a trace of the PHP Fibonacci generator from a previous article as it calculates the fourth Fibonacci number:
Listing 10. PHP Fibonacci generator trace
TRACE START [2007-06-06 14:17:17]
0.0004 16432 +16432 -> {main}() /var/www/catalog/t/fibonacci.php:0
0.0006 16696 +264 -> fib('4') /var/www/catalog/t/fibonacci.php:35
0.0007 16696 +0 -> fib(3) /var/www/catalog/t/fibonacci.php:7
0.0007 16736 +40 -> fib(2) /var/www/catalog/t/fibonacci.php:7
0.0007 16848 +112 -> fib(1) /var/www/catalog/t/fibonacci.php:7
>=> 1
0.0008 16904 +56 -> fib(0) /var/www/catalog/t/fibonacci.php:7
>=> 0
>=> 1
0.0009 16904 +0 -> fib(1) /var/www/catalog/t/fibonacci.php:7
>=> 1
>=> 2
0.0009 16904 +0 -> fib(2) /var/www/catalog/t/fibonacci.php:7
0.0009 16904 +0 -> fib(1) /var/www/catalog/t/fibonacci.php:7
>=> 1
0.0010 16904 +0 -> fib(0) /var/www/catalog/t/fibonacci.php:7
>=> 0
>=> 1
>=> 3
>=> 1
0.0011 12528
TRACE END [2007-06-06 14:17:17]
|
The first column shows time, the second is accumulated memory use, the third is incremental memory use, and the fourth shows function calls, including the parameters.
The lines marked >=> show the return value from each
function (find the corresponding indented -> to match a
call with its return value). Again, the final >=> 1 is
the return value of main().
If you use vim, the author of Xdebug, Derick Rethans, provides a set of syntax
highlighting hints just for Xdebug traces. The hints are contained in the file xt.vim
within the Xdebug source code package. On modern Linux distributions, simply copy
xt.vim to $VIMRUNTIME/syntax/xt.vim, then run vim
tracefile.xt. Figure 4 shows the Fibonacci trace highlighted in vim.
Figure 4. The vim syntax file for Xdebug traces eases analysis
Tracking down bugs in PHP code can be a challenge. But if you have a development system and can install Xdebug, squashing those bugs becomes a lot easier. Xdebug can show a stack trace, dump even complex variables, track memory usage over time, and allow you to conduct an effective post-mortem when an error or crash occurs (not if, but when).
Learn
-
Visit Xdebug.org.
-
Read the Xdebug documentation.
- Learn more uses for Xdebug in the developerWorks article
"Make PHP apps fast, faster, fastest, Part 2: Profile
your PHP application to find, diagnose, and hasten plodding code."
-
PHP.net is the central resource for PHP developers.
-
Check out the "Recommended PHP reading list."
-
Browse all the PHP content on developerWorks.
-
Expand your PHP skills by checking out IBM developerWorks' PHP project resources.
-
To listen to interesting interviews and discussions for software developers, check out developerWorks podcasts.
-
Using a database with PHP? Check out the Zend Core for
IBM, a seamless, out-of-the-box, easy-to-install PHP development and production environment that supports IBM DB2 V9.
-
Stay current with developerWorks' Technical events and webcasts.
-
Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.
-
Watch and learn about IBM and open source technologies and product functions with the no-cost developerWorks On demand demos.
Get products and technologies
-
Download the Xdebug extension.
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
-
Download IBM product evaluation versions, and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
Discuss
-
Join the Xdebug mailing list. (Clicking this link will launch your default mail client.)
-
Participate in developerWorks blogs and get involved in the developerWorks community.
-
Participate in the developerWorks PHP Forum: Developing PHP applications with IBM Information Management products (DB2, IDS).
Comments (Undergoing maintenance)





