Squash bugs in PHP applications with Zend Debugger

Clever IDE finds and helps you fix bugs interactively

A special application called a debugger probes running code, allowing you to suspend execution arbitrarily, examine objects, explore the call stack, and even change the value of a variable on the fly. Learn how to use a debugger to squash bugs in your PHP code.

Martin Streicher (martin.streicher@gmail.com), Editor in Chief, Linux Magazine

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.



13 November 2007

Also available in Russian

According to popular folklore, the first computer bug was literally a bug — specifically, an ill-fated moth that landed in a relay in the Mark II Aiken Relay Calculator being tested at Harvard University. According to the operator's log entry dated 09 Sep 1947, the moth was the "First actual case of [a] bug being found [in a computer]." You can see the handwritten log entry and the infamous insect in Figure 1.

Figure 1. The infamous Mark II moth
The infamous Mark II moth

In fact, the etymology (or should that be entomology?) of the term bug is much more pesky and seemingly predates the misguided moth by some 70 years. In 1848, Thomas Edison, describing mechanical malfunctions, wrote, "The first step is an intuition and comes with a burst, then difficulties arise — this thing gives out and [it is] then that 'bugs,' as such little faults and difficulties are called — show themselves ...." Evidently, the term had already been coined by Edison's contemporaries as jargon.

If you're a software developer, perhaps you can find some comfort and inspiration knowing that even Edison had to "debug" his inventions. (Edison never used the term debug. It is a more recent addition to language, added to the vernacular of engineers through the argot of World War II airplane mechanics.) Or, perhaps you wish that all bugs were limited to the six-legged variety and all you needed to write perfect code was a USB bug zapper or the electronic equivalent of a Roach Motel. "Computer bugs check in, but they don't check out." (Can someone add this feature to the next release of Subversion?) Wishful thinking.

Alas, as Edison observed, bugs are an inherent part of every engineering endeavor. Again, on the subject of invention, Edison wrote, "Months of intense watching, study, and labor are requisite before commercial success or failure is certainly reached."

Luckily, software developers have tools to facilitate "watching," reducing months to minutes or, at worst, hours or days. This article's antecedent, "Squash bugs in PHP applications with Xdebug," shows a variety of techniques for collecting forensic evidence to "autopsy" the cause of a failure. However, such post-mortem analysis can often be difficult and time-consuming because hypotheses must be deduced, then tested. If you lack vital information, you must reinstrument your code, repeat your steps, and restart your investigation — a process that requires potentially numerous iterations.

This article investigates a far more efficient and effective debugging technique: interactive debugging. A special application called a debugger facilitates probing running code, allowing you to suspend execution at arbitrary breakpoints; examine objects, the call stack and the environment; and even change the value of a variable on the fly.

For this demonstration, you'll use the Zend Debugger — an extension of the Zend engine that can probe a running PHP application proactively. You can download and use the Zend Debugger at no charge. However, to control the Zend Debugger and view its diagnostics, you must pair it with a client application. The client can be rudimentary, running from the command line; or the client can be a full-blown integrated development environment (IDE), featuring an editor, code completion, visual class browsers, and more.

Several open source clients can interoperate with the Zend Debugger, including the open source PHP plug-in for Eclipse. My preferred IDE for PHP, though, is Zend Studio, offered by Zend Technologies, the company behind both the open source and the commercial PHP run-time engines. Unlike PHP itself, Zend Studio is a commercial product. You can download and run the software for 30 days at no charge, which is enough time to try the IDE's many features, but you must then buy a license if you want to continue using it. For me, the cost of the license is worth every penny.

Try each and every client to find something that suits you. For example, many IDEs meld with your favorite editor, so you need not relearn gobs of keyboard accelerators. No matter what you choose, it's likely that when you try a debugger client, you'll wonder how you ever lived without it. Say goodbye to print_r()!

Install Zend Studio and the Zend Debugger

To begin, download and install the Zend Studio and Zend Debugger software. The instructions shown here are based on Mac OS X, but instructions for computers running Linux® and Microsoft® Windows® are similar. (The Zend Studio Web page offers specific instructions for each of those platforms.) In fact, you can install and run Zend Studio on your local system and deploy the debugger on your server to debug code remotely.

Regardless of your platform, make sure that your system has a working version of PHP V4 or PHP V5: The Zend software works with either flavor of PHP. Because Mac OS X currently ships with PHP V4 (V4.4.7, to be exact), this article is based on the older PHP version.

To install the Zend tools:

  1. Go to the Zend Downloads and click Try for Zend Studio.
  2. Create a login, then download the Zend Studio Client V5.5.0a (the latest version at the time of writing) and the Zend Debugger V5.2.6.
  3. When the download is complete, run the Zend Studio installer. After the installer finishes, your system should have a directory named /Applications/Zend Studio 5.5.0/.
  4. Open this folder, open the bin subfolder, then double-click the application named ZDE. An empty IDE similar to Figure 2 should appear.
    Figure 2. The Zend Studio IDE
    The Zend Studio IDE

    Set the IDE aside for the moment to install the debugger.

  5. Download the Zend Debugger software and unpack the file with tar xzvf ZendDebugger-5.2.6-darwin8.6-uni.tar.gz to create a directory named ZendDebugger-5.2.6-darwin8.6-uni.
  6. Change to the new directory and copy the file 4_4_x_comp/ZendDebugger.so to a central directory in which the file is easily found but not haphazardly removed. Assuming that you already have the Xcode development tools installed on your Mac, /Developer/Extras/PHP is a good place for the file:
    $ sudo mkdir -p /Developer/Extras/PHP
    $ sudo chgrp admin /Developer/Extras/PHP
    $ sudo chmod g+rwx /Developer/Extras/PHP
    $ sudo cp 4_4_x_comp/ZendDebugger.so /Developer/Extras/PHP

    The Zend Debugger is a Zend extension and must be enabled in php.ini. In Mac OS X, the php.ini file is typically found in /private/etc/php.ini.

  7. Edit the file and add the following lines:
    zend_extension=/Developer/Extras/PHP/ZendDebugger.so
    zend_debugger.expose_remotely=allowed_hosts 
    zend_debugger.allow_hosts=127.0.0.1

    If you do not have a php.ini file (which is satisfactory and simply means that PHP is using solely its default internal settings), create the file with the lines shown above. The first line loads the extension. The second line restricts connections to the debugger to a list of IP addresses, which you provide in the third line. Again, PHP — including the Zend Debugger — is running locally. Hence, the loopback interface 127.0.0.1, but it could just as easily be running on a remote system. If that system is generally available to the Internet, use the second and third lines to prevent unwanted access to the debugger.

  8. Enable the PHP extension in the Apache HTTP Server on your Mac OS X system. Open the file /private/etc/httpd/httpd.conf and remove the leading # (octothorpe) from the line that begins LoadModule php4_module and from the line that contains AddModule mod_php4.c.
  9. If it's not already running, launch the Apache HTTP Server on your Mac OS X computer.
  10. Open System Preferences, choose Sharing, then enable Personal Web Sharing, as shown in Figure 3. If Personal Web Sharing is already enabled, click Stop, then Start to restart the Web server.
    Figure 3. Enable the Web server on your Mac OS X system
    Enable the Web server on your Mac OS X system

To verify the operation of PHP and your Web server, change directory to ~/Sites/ and create the file info.php with the following contents:

<?php 
  phpinfo(); 
?>

Point your browser to the http://localhost/~username/info.php, where username is your Mac OS X login name. If your configuration is correct, you should see a section labeled Zend Debugger in the phpinfo() results. The section should also reflect the settings of php.ini.

Figure 4. Verify that your debugger is working
Verify that your debugger is working

Make a note of the zend_debugger.connector_port setting. You connect to this port on the Web server to initiate debugging sessions.

By the way, if the specifics of the installation seem daunting or if you'd like to save time, you can download and install either the Zend Core or the Zend Platform. Both are commercial products, but each is preconfigured to include the Zend Debugger. The instructions shown in the remainder of this article work equally well with Zend Core or the Zend Platform.


Connect to the Zend Debugger

The combination of Zend Studio and the Zend Debugger allows you to remotely control the Zend engine. Zend Studio acts as your control panel, displaying information about the inner machinations of the engine. You can see the source code that's running, the state of variables, and the state of the Web environment, among other data.

The Zend Debugger is something of a proxy: It relays information from the Zend engine to Zend Studio and relays commands from Zend Studio to the Zend engine. Commands include debugging concepts, such as continue, which resumes execution, step, which advances execution one statement, and stop, which terminates execution. Whenever the state of the Zend engine changes, the debugger relays the changes to Zend Studio to display.

Thus, to debug a PHP application, you configure Zend Studio to connect to the debugger, then visit the application in your browser as before. When the application starts to run, the debugger intercedes and passes control to Zend Studio. That's when you take over.

Configure Zend Studio

Here's a simple way to establish the connection to the Zend Debugger (afterward, I provide a more complex script and explore more debugging tricks):

  1. Launch the Zend Studio IDE.
  2. Choose Zend Studio > Preferences, then click the Debug tab:
    • Here, the Web server (with the Zend Debugger embedded) is separate from the Zend Studio application. Set Debug Mode to Server.
    • For the purposes of this demonstration, all the PHP applications will be created in your personal Web-site directory in your home folder. Specify localhost/~username/ as the Debug Server URL. (Specifically, this can be any path on the server that contains a PHP file.) You use this setting, combined with the Dummy File setting, to enable debugging in the Zend engine.) My Mac OS X user name is mstreicher, so I'd specify localhost/~mstreicher/. See Figure 5.
    • The debugger uses Client IP to connect back to the system running Zend Studio. Usually, you can leave this setting as Default, but because both client and server are running locally, choose Customized and provide an IP address of localhost.
    • You can set Dummy File to the name of any PHP file found in the Debug Server URL. Dummy File acts as a bootstrap: Whenever you start a debugging session, the URL Debug Server URL/Dummy File?start_debug=1&debug_host=Client IP&... configures and enables the debugger. If the Web server cannot find Dummy File, the debugger won't work. (If you're running Zend Studio on your laptop, debugging your application code on a remote server, be sure to place the named Dummy File within the document root of the application on the remote server.) To verify the existence of the Dummy File, simply type its URL into your browser. Everything is configured properly if you see no errors.)
    • Set Client Debug Port to any nonprivileged port, such as 10000.
    • You can accept the defaults for all the other settings on the Debug tab. When you're finished, your window should resemble Figure 5.
      Figure 5. Sample settings to connect to the Zend Debugger
      Sample settings to connect to the Zend Debugger
    • Click OK.

At this point, you're ready to write and debug PHP code.

Debug some simple code

Zend Studio combines everything you need to write PHP code: an editor to edit individual files; a file manager to collect many files into a project; a PHP class and module browser to use as a reference, an output view to see intermediate results; and an environment browser to peer directly into variables, the call stack, and the output buffer. Better yet, the editor is context-sensitive and can automatically complete PHP control structures, keywords, and string delimiters.

To begin, click the mouse in the Editor window found at the top center in Zend Studio. Type the snippet of code shown in Listing 1 (to experience the completion features of the editor), which is supposed to print the digits 1-10.

Listing 1. Print digits 1-10
<?php
    $counter = 1;
    
    while ( $counter < 10; ) {
        printf( "%d\n", $counter );
        
        increment( $counter );
    }
    
    function increment( $i ) {
        $i++;
        
        return;
    }
?>

When the code is finished, click the green Play button in the toolbar to run the code. Rather than print 1-10, the code prints an infinite list of the numeral 1. Click the red Stop button to terminate the program.

Obviously, there's a bug. The first line of inquiry: What's happening to $counter? To find out, stop execution at lines 7 and 11. The former pause allows you time to peek at the value of $counter before the call to increment(); the latter suspension lets you see what's being passed in as an argument and what happens to the local variable $i. To set a breakpoint at line 7, place the cursor in line 7, then choose Debug > Add/Remove Breakpoint. Line 7 should be highlighted in red to indicate that it has a breakpoint. Do the same for line 11. If you want to see an inventory of your breakpoints, click the Breakpoints tab in the Debug window at the bottom. Figure 6 shows the IDE window after the breakpoints have been set.

Figure 6. Breakpoints set in the code sample
Breakpoints set in the code sample

Next, click Play to restart the application. Execution starts and immediately pauses at line 7. The Debug Output pane (at far right) contains some output, though, and the Variables tab in the Debug window shows a list of variables. Arrays are denoted with brackets ([]); simple scalars are marked with a blue circle. You should see that $counter = (int) 1.

Now click the blue down arrow on the toolbar to advance one statement into the function. (Compare this to the blue right arrow and bar, which calls the function and advances to the next statement in the caller.) Your cursor should now be in line 11, and the Variables tab should only show one variable: the formal argument $i = (int) 1. So far, so good.

Click down arrow again to advance past line 11. $i is now 2. Click the Stack tab in the Debug window. Figure 7 captures the scene.

Figure 7. A stack backtrace
A stack backtrace

Click the down arrow again to return to the caller at line 5. Surprisingly, $counter is still 1 — and the root of the first bug. The increment() function should be called with a reference to the variable instead of the value of the variable. Easily fixed! Just change the function call to read increment( &$counter ).

Click Stop, then click Play again. Step through the program, and you should see 1, 2, 3, . . . 8, 9. What happened to 10? The answer is obvious: It's an "off-by-one error." Simply change < to <=. However, you can watch the error occur if you delete the previous two breakpoints and set a new breakpoint at line 4.

Although this example is somewhat contrived, it nonetheless demonstrates several fundamental interactive debugging techniques:

  • Step through your program one instruction at a time to monitor variables and discover when things go awry.
  • If a function is called from many places, use the stack backtrace to determine the specific calling sequence. Often, you can trace the origin of a bug back to a caller that's sending the wrong type of argument, the wrong number of arguments, or a mismatch between the order of actual and formal arguments. You can also expand an entry in a backtrace — click the little right arrow to the left of the name — to see the values of the function's formal arguments.
  • Use breakpoints to pause execution to probe state. For example, if the code of a function is suspect, place a breakpoint at the first statement of the function, wait for execution to pause, look at the stack backtrace, and step through the function to find the bug.
  • Use the Variables tab in the Debug window to examine environment, global, and local variables.

To learn more about Zend Studio, use the IDE (at least temporarily) to write PHP code, and click all the tabs to discover its many features. Even if you choose to edit code in another program, you can use Zend Studio to debug classes, methods, and unit tests. Writing the latter in the IDE is especially helpful.


Debug your PHP application

Of course, the real value of a PHP debugger is peering into your Web application in real time. Here's just such a scenario.

Baking the pizza application

Listing 2 and Listing 3 show a simple PHP application with a class, methods, and some wrapper code. The application shown in Listing 2, index.php, creates pizza orders using the Pizza class (shown in Listing 3) to calculate the price of each pizza. The code isn't complex, but it suffices here because it has a couple of bugs. One is obvious: Every pizza is the same price, independent of the number of toppings. The second is a matter of protocol: The same topping shouldn't be charged for twice.

Listing 2. The pizza order application, index.php
<?php
    include_once( 'pizza.class.php' );
    
    $large = new Pizza( 'L', null );
    echo $large->price() . '<br />';
    
    $toppings = array();
    $toppings[] = 'pepperoni';
    $toppings[] = 'sausage';
    $xl = new Pizza( 'XL', $toppings );
    echo $xl->price() . '<br />';
    
    $special = new Pizza( 'XL' );
    $special->add( null );
    $special->add( 'pepperoni' );   
    $special->add( 'pepperoni' );   
    $special->add( 'anchovies' );   
    $special->add( 'anchovies' );
    $special->add( 'olives' );
    echo $special->price() . '<br />';
?>
Listing 3. The delicious Pizza class
<?php
    class Pizza {
        var $size;
        var $toppings = array();
        var $price;
        
        function Pizza( $size = "R", $toppings = null ) {
            $this->size = $size;
            $this->toppings = $toppings; 
        }
        
        function size() {
            return( $this->size );
        }
        
        function price() {
            $this->price = 10;
            $multiplier = 1;
            
            switch ( $this->size ) {
                case 'L': 
                    $multiplier = 1.25;
                    break;
                case 'XL':
                    $multiplier = 2;
                    break;
            }
            
            $this->price = $this->price + 
                ( sizeof( $this->toppings() ) ) * $multipler;
                
            return( $this->price );
        }
        
        function toppings() {
            return( $this->toppings );
        }
        
        function add( $topping ) {
            $this->toppings[] = $topping;
        }
    }
?>

To create the project:

  1. Open Zend Studio and create a new project by choosing Project > New Project.
  2. Name the project pizza (as shown in Figure 8), then click Next.
    Figure 8. Creating the new project, pizza
    Creating the new project, pizza

    In the window that appears, you can add one or more source code directories to the project, making it easy to find, open, edit, and save the entirety of the code from within the IDE. Because this is a new project, add a new directory for the source code.

  3. Click Add path, navigate to your personal Sites directory, then click New Folder (the third button at the top right).
  4. Type the name pizza, click that entry (as shown in Figure 9), then click Add.
    Figure 9. Creating a new folder for the source code
    Creating a new folder for the source code
    The wizard window should now look like Figure 10.
    Figure 10. The project now has one source code folder
    The project now has one source code folder
  5. Click Next.

    The next collection of settings specifies how to connect to your (debug) Web server.

  6. Clear the Use System Defaults check box and type the URL to your application, which is likely to be localhost/~username/pizza/index.php, if you' re walking through the tutorial on Mac OS X (or perhaps just localhost/pizza/index.php if you're using a Linux® machine, say, for your server and Mac OS X for Zend Studio.) Figure 11 captures the settings.
    Figure 11. Setting the URL for the project
    Setting the URL for the project
  7. Click Finish.

    Your Project window should now have a folder named pizza.

  8. To continue, choose File > New File twice.
  9. Copy (or better yet, type) Listing 2, and save the text to a new file named index.php in the pizza folder on the file system.
  10. Type the text of Listing 3, and save it as Pizza.class.php in the pizza folder.
  11. Expand the folder in your Project pane to see the two PHP files.
    Figure 12. The expanded project folder
    The expanded project folder

What's the matter with my code?

You're ready to debug the application. Click Play: The application runs, and its output is captured in the Debug Output pane. The output looks like this:

Content-type: text/html
10<br />10<br />10<br />

Why are all three pizzas $10? If you look at the Debug Messages pane, the answer is spelled out: The variable $multipler is undefined because it's a typo. It should be $multiplier.

You can catch this error another way, too, in case the bug isn't so obvious next time (which is usually the norm). Double-click Pizza.class.php in the project, place your cursor in line 29 and choose Debug > Add/Remove Breakpoint. The line turns pink. Double-click index.php and click Play. Execution stops at line 29. If you look in the Debug window, $this->price is correct so far, and $multiplier is correct, so what happens? Click the down arrow to step through and see that the price fails to change. Ah! A typo. Fix that, rerun the application, and the prices should change for each pizza.

As an exercise, use the IDE to step through the other bug — a logic error that can cause an overcharge. Clear all breakpoints, then set a breakpoint at line 14 in index.php. Set breakpoints at functions toppings() and add() to watch what happens to the Pizza object. Figure 13 shows what the object looks like during the calculation of the price.

Figure 13. A pizza gone bad
A pizza gone bad

To fix the bug, disallow null as a valid topping (really the fault of index.php, but you can idiot-proof the add() function) and remove duplicates in the toppings. The following code addresses the problems.

        function toppings() {
            if ( is_array( $this->toppings ) ) {
                return( array_unique( $this->toppings ) );
            }
            
            return( 0 );
        }
        
        function add( $topping ) {
            if ( ! is_null( $topping ) ) {
                $this->toppings[] = $topping;
            }
        }

Connecting to the browser

The last exercise is to launch the application in the browser and debug it in the IDE. This is the typical scenario: Watch the output in the browser, interact with the Web pages, then step through the application to monitor how it responds:

  1. Choose Debug > Debug URL.

    It's likely that the default settings suffice. If not, set the Open Browser At field to the URL of the index.php file. For this example, because all the software runs on the Mac, choose Local files, if available.

  2. Click OK.

    The browser opens the URL you specified for Open Browser At (with a long list of parameters to configure the debugger for this debugging session). The browser is initially blank because the IDE has paused execution at the very beginning of the application.

  3. Switch to the IDE and place the cursor in the first line of index.php, just about to include the class file. You can step through the application just as you did before.

If the application had forms, you could set a breakpoint at the form handler and view the incoming parameters. All the global environment variables, PHP globals, and the parameters of each Web server request are available in the Debug window.

Use the Debug URL window to control when your application pauses in the debugger. Typically, a PHP Web application is centralized in one directory and contains a main landing page, commonly named index.php. Links on this "home" page point to other PHP pages that encapsulate features. For example, the URL .../store/index.php may represent the home page of an online store; another URL, .../store/cart/index.php, may realize the shopping cart.

You can configure Zend Studio to debug one, some, or all PHP pages located within a particular application root. For instance, if your cart has errors, you can launch a debug session whenever you open any page in cart/. Or, you can simply start the session if the landing page, index.php, is browsed, and debug the entire application.

Better yet, you can combine Zend Studio, either Internet Explorer® or Mozilla Firefox, and the Zend Debugger Browser Toolbar to launch a debug session immediately from your browser. You can debug the current page, all pages on the site, or the operation of a single form.


A new way to work

An interactive debugger is like a little light bulb popping on above your head. Tasks that required laborious diagnostics at each step of a function suddenly become easy — set a breakpoint, poke around for as long as you'd like, and watch what happens in each statement along the way. Keep a notepad handy and jot down cause and effect, observations, and hypotheses.

Zend Studio isn't your only choice for a debugger. Try the others and find the one that suits your work style best. Whatever you do, use the debugger. You'll wonder how you ever wrote code without it.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=266583
ArticleTitle=Squash bugs in PHP applications with Zend Debugger
publish-date=11132007