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

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

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.

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.

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

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

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

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.

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:

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.

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.

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

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

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

Launch the Zend Studio IDE.

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

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

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

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

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.

Open Zend Studio and create a new project by choosing Project > New Project.

Name the project pizza (as shown in Figure 8), then click Next.

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

Click Add path, navigate to your personal Sites directory, then click New
Folder (the third button at the top right).

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

The wizard window should now look like Figure 10.

Figure 10. The project now has one source code folder

Click Next.

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

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

Click Finish.

Your Project window should now have a folder named pizza.

To continue, choose File > New File twice.

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.

Type the text of Listing 3, and save it as Pizza.class.php in the pizza folder.

Expand the folder in your Project pane to see the two PHP files.

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

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.

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:

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.

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.

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.

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.