Debugging your PHP Code: XDebug on MAMP with TextMate and MacGDBp Support

Mar 16 2009

As I see it, there are two major drawbacks to the otherwise-spectacular MAMP (MacOS Apache MySQL, PHP) package (three if you count the funky directory structuring): 1. The .h files are all missing, so PECL doesn't work very well. 2. There is no debugger. The first issue is covered elsewhere. In this article, I will address the second by explaining how to setup XDebug on MAMP. XDebug is one of the two popular PHP debugging engines (With ZendDB being the other). In this article we will cover the following: - Getting and installing XDebug - Using XDebug for basic stack tracing - Integrating XDebug with TextMate - Configuring XDebug and MacGDBp for client/server debugging - Using MacGDBp By the end of this article, you should be able to successfully debug PHP applications running on your local MAMP server. Initially, I was going to re-build PHP from source and then build the PECL module for XDebug. However, I have happily discovered that this is not necessary. ActiveState has binary packages of XDebug for Mac OS X available for free from their website! At the time of this writing, the ActiveState package contains XDebug 2.1.0-dev. Unpack the Debugger Grab a copy of the latest debugger from ActiveState, and then untar it. You will get a directory named something like this: ~~~ Komodo-PHPRemoteDebugging-5.1.0-alpha1-26259-macosx-x86/ ~~~ Go into that directory. Inside of that directory, there are several numbered subdirectories, correlated with the version numbers of various PHP releases. MAMP uses PHP 5.2: ~~~ $ cd Komodo-PHPRemoteDebugging-5.1.0-alpha1-26259-macosx-x86/ $ cd 5.2 ~~~ Now we need to copy the xdebug.so file to the correct location within MAMP: ~~~ $ cp 5.2/xdebug.so /Applications/MAMP/bin/php5/lib/php/extensions/no-debug-non-zts-20050922/ ~~~ Now we have XDebug's shared object file in the correct place. The next step is to configure it. Configuring XDebug Next, configure PHP to load the xdebug module in the PHP ini file (php.ini). This file is located in /Applications/MAMP/conf/php5/. Near the end of the file, you will need to add a couple of lines like this: ~~~ ini [xdebug] zend_extension=/Applications/MAMP/bin/php5/lib/php/extensions/no-debug-non-zts-20050922/xdebug.so xdebug.file_link_format="txmt://open?url=file://%f&line=%1" ~~~ This defines a new extension (xdebug) and points PHP to the extension's shared object file. The last line of the code above is optional. It tells xdebug to use TextMate to open files for viewing. UPDATE: As sdboyer points out in the comments below, the Zend Optimizer, turned on by default in MAMP, must be disabled for XDebug to function correctly. Comment out the following lines in php.ini ~~~ ini ;zend_optimizer.optimization_level=15 ;zend_extension_manager.optimizer=/Applications/MAMP/bin/php5/zend/lib/Optimizer-3.3.3 ;zend_optimizer.version=3.3.3 ~~~ Restart MAMP. The debugger should now be installed. The next time you get an error, XDebug will display a message that looks something like this:

In just a moment, we will take a detailed look at such errors.

Using XDebug

XDebug provides a number of facilities that you may find useful. It can produce code coverage reports, performance analyses, and detailed debugging messages.

But the first thing you are likely to notice is XDebug's stack trace messages.

Once you have XDebug installed as described above, the next time a PHP error occurs on your server you will notice something new. The old-style PHP errors and warnings are gone. Instead of seeing "PHP Error: Some message in somefile.php" you will get a detailed stack trace.

Let's take a look at an example. Here is a broken PHP script:

  <?php
  require 'tweet/QueryPath/QueryPath.php';

  qp(QueryPath::HTML_STUB, 'body')->append('<p>Test</b>');
  ?>

The error in the script above is that the HTML passed into the append() function has mismatched tags. (Visit QueryPath.org for more info on the QueryPath library.)

Here's a partial view of wheat XDebug generates when the above script is run:

This colorful table provides a wealth of information about the error. At the top, it shows the error message. Then it shows the stack trace, which is (roughly speaking) the list of functions from outermost to innermost that were currently being called when the script erred.

In other words, we can tell from the stack trace that {main} (the main script) was executing. It called QueryPathImpl->append() at line 4, which in turn called QueryPathImpl->prepareInsert() at line 416 of QueryPathImpl.php. That, in turn, called DomDocumentFragment->append() on line 650 of QueryPathImpl.php.

Using this information, we can pinpoint where the error happened. The code caused an error when DomDocumentFragment->append() parsed the broken piece of HTML. By following the stack trace from top to bottom, we can pinpoint (in our code) what the cause of these circumstances was. In our case, of course, it was our passing of a badly formed XML fragment.

Stack traces can provide a greater level of detail, making many bugs much easier to find. But sometimes you need (or want) to know more about what is going on behind the scenes. That's where a client-side debugger can really come in handy.

Hooking XDebug to a GUI Debugger

Many PHP IDEs provide integration with the XDebug debugger. But to use such debuggers, you will need to use the IDE for your projects. But sometimes a hefty IDE is not the best application development environment. Some developers (myself included) prefer lighter-weight editors like vi, TextMate, Coda, and BBEdit. In these cases, an integrated IDE may not be an attractive or realistic proposition. Fortunately, there is a solution that fits this problem.

There are also stand-alone clients that provide simple debugging interfaces. In this section, we will look at the MacGDBp debugger, a simple tool for debugging.

MacGDBp provides a basic debugging interface. It does not provide performance testing or coverage analysis. Rather, it simply provides a front-end for stepping through PHP code as it executes. Of course, this is the most commonly used feature of a debugger. It provides a visual interface for viewing code and setting/clearing breakpoints, as well as common debugging features. Later, we will look at the interface in more detail.

You can download the latest version of MacGDBp at http://www.bluestatic.org.

When a debugging client is used with XDebug, there is some extra configuration which must be done. XDebug must be configured to communicate with an external debugging client. And, of course, the client must be configured to communicate with XDebug. Let's take a look at this configuration, which is particularly easy for MacGDBp and XDebug.

Configuring the Server

The first step is to configure the php.ini file to allow XDebug to connect to debugging clients. We will need to revisit the same section of php.ini that we created above. What we need to do there is tell the debugger that we now want it to send debugging messages to MacDBGp.

Here's the relevant section of the php.ini file:

  [xdebug]
  zend_extension=/Applications/MAMP/bin/php5/lib/php/extensions/no-debug-non-zts-20050922/xdebug.so
  xdebug.remote_enable = On
  xdebug.remote_autostart = 1

The first two lines remain the same. But we add two new parameters:

  • xdebug.remote_enable: This tells the PHP Apache module that we want to use remote debugging.
  • xdebug.remote_autostart: This tells PHP that we want it to immediately try to connect itself to a debugger as soon as it initializes.

The second directive is not necessary, and MacGDBp can be used without it. But in my experience, it performs better with auto-start turned on. Do not do this on a production server. This configuration should only be used on a development server on a secure network.

With this configuration, XDebug will attempt to connect to a debugger running on port 9000 of the localhost.

There are other configuration options for XDebug. You may want to check out the XDebug manual to learn about other supported configuration options. But what we have enabled here is sufficient for general debugging on a local workstation running MAMP and MacGDBp. This configuration will support HTTP POST operations.

Make sure you restart MAMP so that these changes take place.

Configuring MacGDBp

Configuring MacGDBp is even easier than the PHP configuration.

Once you have MacGDBp installed, simply open it up. You may want to change the IDE key (a shared key that the debugging server and debugging client both use). To do this, go to the MacGDBp > Preferences menu and change the key there. I will be using the default key, macgdbp.

That's all there is to it. The defaults for MacGDBp should be sufficient for our needs.

Starting a Debug Session

Now that we have a configured server and a listening client, we are ready to give this a test run. I will use the same script I began with:

  <?php
  require 'tweet/QueryPath/QueryPath.php';


  qp(QueryPath::HTML_STUB, 'body')->append('<p>Test</b>');
  ?>

In my browser, I will type in the appropriate URL: http://localhost:8888/err.php

(Note: If you do not turn on xdebug.remoteautostart in php.ini, you will have to add the URL parameter: ?XDEBUGSESSION_START=macgdbp to get it to start.)

The page will not immediately load. In fact, the browser will sit there waiting for the server until it times out. How come? Because the server is waiting on the debugger.

When debugging code, the server awaits the instructions of the debugging client. After loading the code, it will not begin executing until the debugger sends it an instruction telling it to.

Looking at the debugger, we should be able to see a screen that looks something like this:

This is the main debugger window. At the top are the buttons used to control the debugging process. Beneath the button bar are two panes. The one on the left displays all of the variables currently in scope. As you progress through a debugging session, these values will change. You can watch them to see what is happening as you go.

The right pane shows the call stack. This provides a quick way of correlating where we are in the program's execution with locations in the source code.

Beneath these two panes is the main code window. This displays the source code that is currently being executed. The line about to be executed is highlighted in red.

To begin the debugging process, let's take a look at the button bar.

From left to right, the buttons are:

  • Step into: Clicking this will advance the debugger one step and, if necessary, go "into" the function, class, file, etc. By clicking this button repeatedly, you can step through every part of the process.
  • Step out: This will move up a level. For example, if you are mid-way through a function and you step out, the debugger will advance to the next part of the code, skipping the remainder of the function.
  • Step Over (Skip): This will skip examining the next step. For example, if the current line is pointing to a function, and you do not want to see the function execute, you can skip it with this button. Clicking this will simply advance to the next line in the current scope.
    • Continue (Play): Continue execution until either the next breakpoint or the end of the script.
    • Reset: Remove the current debugging session and wait for the next request from the server.

Using these buttons, you can navigate through the execution of a script. While this will give you a detailed step-by-step analysis of a program as it executes, it can often be overkill. More often, you will want to analyze just a small part of your script. That is best accomplished with breakpoints.

A breakpoint is a marker that tells the debugger to pause when the location is reached. Using breakpoints, you can set the debugger to play through most of the code, pausing only when it gets to the portion of interest.

To set breakpoints in MacGDBp, navigate to the Windows menu and click on Breakpoints. There you can view and edit breakpoints.

To add a new breakpoint, click on the + button in the lower-left and then select the desired file from within MAMP's htdocs directory. Once you are viewing the file, you can set a breakpoint by clicking on a line number. This will overlay a blue arrow on top of the line number. (Clearing the breakpoint is done by clicking on the arrow.)

You can set as many breakpoints as you want.

Once breakpoints are set, clicking on the Continue button during a debug session will cause the code to execute until it hits the next breakpoint. This method can be much faster than stepping through the code.

Once you have finished executing a script, the Reset button will be activated. You will not be able to start another debugging session until you have reset the debugger.

Conclusion

That is all there is to configuring and using XDebug with MAMP, TextMate, and MacGDBp.

But we have come nowhere near exhausting the capabilities of XDebug. With profiling and cache grinding, you can better understand how long your program takes, and what areas are bottlenecks. With coverage analysis (in XDebug 2), you can determine how much of the code is being exercised -- an invaluable tool for automated testing. In fact, in XDebug 2, you can even interact with the debugger directly.

For now, though, the basics are covered. You should be able to install XDebug on MAMP, configure it to use TextMate and print stack traces, and also set it up to send debugging information to MacGDBp.