Skip to main content

Squash bugs in PHP applications with Xdebug

PHP debugger works better than using echo with var_dump(), debug_zval_dump(), and print_r()

Martin Streicher (martin.streicher@gmail.com), Editor-in-Chief, McClatchy Interactive
Martin Streicher is chief technology officer for McClatchy Interactive, editor-in-chief of Linux Magazine, a Web developer, and a regular contributor to developerWorks. He earned a master's degree in computer science from Purdue University and has been programming UNIX-like systems since 1986.

Summary:  The Xdebug extension for PHP helps you autopsy your application when an error or crash occurs. Learn how to use Xdebug to trace the call stack, analyze memory usage, and comb through the contents of arguments and variables.

Date:  07 Aug 2007
Level:  Intermediate
Activity:  3335 views

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.

Like C.S.I., only geekier

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}


Build and install Xdebug

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:

  1. Download the Xdebug tarball (a gzip-compressed .tar archive). The wget command makes the job easy:
     $ wget http://www.xdebug.org/files/xdebug-2.0.0RC4.tgz
                        

  2. Extract the tarball and change to the directory of the source code:
    $ tar xzf xdebug-2.0.0RC4.tgz
    $ cd xdebug-2.0.0RC4
                        

  3. Run phpize to 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 of phpize is a script — aptly named configure — used to tailor the rest of the build process.
  4. 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
    

  5. 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).
    

    The make yields the Xdebug extension, xdebug.so.
  6. 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.
  7. 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
Xdebug extension enabled

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.


Configuring Xdebug

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.

Limit recursion

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
Too much recursion

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.

Answer the four w's

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.

Put it together

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.


Interpret a dump report

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
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.


Trace your code

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
A highlighted trace in vim


Pesky PHP pests imperiled

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).


Resources

Learn

Get products and technologies

Discuss

About the author

Martin Streicher is chief technology officer for McClatchy Interactive, editor-in-chief of Linux Magazine, a Web developer, and a regular contributor to developerWorks. He earned a master's degree in computer science from Purdue University and has been programming UNIX-like systems since 1986.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=244689
ArticleTitle=Squash bugs in PHP applications with Xdebug
publish-date=08072007
author1-email=martin.streicher@gmail.com
author1-email-cc=martin.streicher@linux-mag.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers