Advertisement

Xdebug - Professional PHP Debugging

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

Our Agenda

  1. Introduction to the topic.
  2. Downloading and installing Xdebug on your local machine (Mac OS X 10.6.6+, MAMP 2.1.1).
  3. Integrating with PhpStorm.
  4. Practice debugging.


What You Will Need

  • A Mac running Mac OS X 10.6.6+.
    • If you are on 10.8.X you may need to install XQuartz as Apple removed X11.
    • If you are on Windows, the whole process is somewhat easier, just hit Google for more details.
  • Apple Xcode 4.6 (free on the Mac App Store).
    • Command Line Tools.
  • Homebrew.
  • A terminal app of your choice.
  • PhpStorm 5+ (many other IDE's will work as well).

What Is Xdebug?

Well, technically, Xdebug is an extension for PHP to make your life easier while debugging your code. Right now, you may be used to debugging your code with various other simple solutions. These include using echo statements at different states within your program to find out if your application passes a condition or to get the value of a certain variable. Furthermore, you might often use functions like var_dump, print_r or others to inspect objects and arrays.

What I often come across are little helper functions, like this one for instance:

function dump($value) {
    echo ‘<pre>';
    var_dump($value);
    echo ‘</pre>';
}

The truth is, I used to do this too, for a very long time actually.

The truth is, I used to do this too, for a very long time actually. So what's wrong with it? Technically, there is nothing wrong with it. It works and does what it should do.

But just imagine for a moment, as your applications evolve, you might get into the habit of sprinkling your code all over with little echos, var_dumps and custom debuggers. Now granted, this isn't obstructive during your testing workflow, but what if you forget to clean out some of that debug code before it goes to production? This can cause some pretty scary issues, as those tiny debuggers may even find their way into version control and stay there for a long time.

The next question is: how do you debug in production? Again, imagine you're surfing one of your favorite web-services and suddenly you get a big array dump of debug information presented to you on screen. Now of course it may disappear after the next browser refresh, but it's not a very good experience for the user of the website.

Now lastly, have you ever wished to be able to step through your code, line by line, watch expressions, and even step into a function call to see why it's producing the wrong return value?

Well, you should definitely dig into the world of professional debugging with Xdebug, as it can solve all of the problems above.


Configuring MAMP

I don't want to go too deep into the downloading and installation process of MAMP on a Mac. Instead, I'll just share with you that I'm using PHP 5.4.4 and the standard Apache Port (80) throughout this read.


Your First Decision

A quick note before we start with building our own Xdebug via Homebrew: If you want to take the easiest route, MAMP already comes with Xdebug 2.2.0. To enable it, open:

/Applications/MAMP/bin/php/php5.4.4/conf/php.ini

with a text editor of your choice, go to the very bottom and uncomment the very last line by removing the ;.

The last two lines of the file should read like this:

[xdebug]
zend_extension="/Applications/MAMP/bin/php/php5.4.4/lib/php/extensions/ no-debug-non-zts-20100525/xdebug.so"

Now if you're asking yourself:

“Why would I want to choose a harder way than this one?”

And my answer to that is, it is never a mistake to look beyond your rim and learn something new. Especially as a developer these days, throwing an eye on server related stuff will always come in handy at some point in time. Promised.


Install Xcode and Command Line Tools

You can get Apple Xcode for free off of the Mac App Store. Once you've downloaded it, please go to the application preferences, hit the "Downloads" tab and install the "Command Line Tools" from the list.


Install Homebrew

Homebrew is a neat little package manager for Mac OS X which gets you all the stuff Apple left out. To install Homebrew, just paste the following command into your terminal.

ruby -e "$(curl -fsSkL raw.github.com/mxcl/homebrew/go)"

On a Mac, Homebrew will be the most convenient way to install Xdebug. On Linux however, compiling it yourself is the best way to go; which is not that easy on a Mac.

Tip: Windows users just need to download the *.dll file from Xdebug.org, put it into the XAMPP folder and add the path to their php.ini file.

As a PHP developer, you should from now on be aware of Jose Gonzalez's "homebrew-php" Github repo, which holds a lot of useful "brews" for you. If you've ever asked yourself how to install PHP 5.4 manually, you are right there.

Now if you get into any trouble while installing Homebrew, check out Jose's Readme.

To complete our Homebrew excursion, we want to "tap" into Jose's brewing formulae by executing the following commands within your terminal application:

brew tap homebrew/dupes

This will get us some dependencies we need for Jose's formulae.

brew tap josegonzalez/homebrew-php

Done! Now we should be ready to install Xdebug the comfy way, on a Mac.


Install Xdebug

Back in your terminal application, please execute:

brew install php54-xdebug

If you are on PHP 5.3, just replace the "4" with a "3" ;)

The installation will take some time. After it's done, you'll see a little beer icon and some further instructions which you can ignore.

So what just happened? Homebrew downloaded all the files including their dependencies and built them for you. As I've already told you, compiling yourself on a Mac can be a hassle. At the end, we got a freshly compiled xdebug.so located at /usr/local/Cellar/php54-xdebug/2.2.1/.

Attention: Please note that Homebrew will install PHP 5.4 to your system during the process. This should not influence anything as it is not enabled on your system.

To finally install Xdebug, we just need to follow a few more steps.

Change directory (cd) to MAMP's extensions folder:

cd /Applications/MAMP/bin/php/php5.4.4/lib/php/extensions/no-debug-non-zts-20100525

You can re-check the path by looking at the last line of /Applications/MAMP/bin/php/php5.4.4/conf/php.ini, as this is where we are going.

Backup the existing xdebug.so just in case:

mv xdebug.so xdebug.so.bak

Then copy your Homebrew Xdebug build:

cp /usr/local/Cellar/php54-xdebug/2.2.1/xdebug.so /Applications/MAMP/bin/php/php5.4.4/lib/php/extensions/no-debug-non-zts-20100525/

If you want to force a copy (cp) command to overwrite existing files, just do cp -X source target.

Last, but not least, we need to modify the php.ini file to load the Xdebug extension file. Open /Applications/MAMP/bin/php/php5.4.4/conf/php.ini with a text editor of your choice, go to the very bottom and uncomment the last line by removing the semicolon at the front. Don't close the file just yet.

Now relaunch MAMP, go to http://localhost/MAMP/phpinfo.php. If everything went well, you should find this within the output:

xdebug-in-phpinfo

If it did not work, please make sure that you really copied over the xdebug.so and have the right path in your php.ini file.


Start Debugging

Before we can actually start debugging, we need to enable Xdebug. Therefore, I hope you didn't close out your php.ini, as we need to add this line to the very end, after the zend_extension option:

xdebug.remote_enable = On

Save and close your php.ini file and restart MAMP. Go to http://localhost/MAMP/phpinfo.php again and search for xdebug.remote on the site. Your values should look exactly like mine:

xdebug-remote-in-phpinfo

If they do not, follow the same procedure you used to add remote_enable = On for the other statements at the end of your php.ini file.

Now, open your IDE of choice. You can use Xdebug with a number of popular software solutions like Eclipse, Netbeans, PhpStorm and also Sublime Text. Like I said before, I am going to use PhpStorm EAP 6 for this demo.

Inside of PhpStorm, open the application preferences and find your way to "PHP \ Debug \ DBGp Proxy" on the left hand side, like in the screenshot below:

phpstorm-debug-settings

Now choose your personal IDE key. This can be any alphanumeric string you want. I prefer to just call it PHPSTORM, but XDEBUG_IDE or myname would be perfectly fine too. It is important to set the "Port" value to 9000 as our standard Xdebug configuration uses this port to connect to the IDE.

Tip: If you need to adjust this, add xdebug.remote_port = portnumber to your php.ini file.

Attention: Other components may change this value inside of PhpStorm, so watch out for it if something fails.

Next, click that red little phone button with a tiny bug next to it on the top toolbar. It should turn green. This makes PhpStorm listen for any incoming Xdebug connections.

phpstorm-bug-phone

Now we need to create something to debug. Create a new PHP file, call it whatever you'd like and paste in the following code:

<?php

// Declare data file name
$dataFile = 'data.json';

// Load our data
$data = loadData($dataFile);

// Could we load the data?
if (!$data) {
    die('Could not load data');
}

if (!isset($data['hitCount'])) {
    $data['hitCount'] = 1;
}
else {
    $data['hitCount'] += 1;
}

$result = saveData($data, $dataFile);

echo ($result) ? 'Success' : 'Error';

function loadData($file)
{
    // Does the file exist?
    if (!file_exists($file)) {
        // Well, just create it now
        // Save an empty array encoded to JSON in it
        file_put_contents($file, json_encode(array()));
    }

    // Get JSON data
    $jsonData = file_get_contents($file);
    $phpData  = json_decode($jsonData);

    return ($phpData) ? $phpData : false;
}

function saveData($array, $file)
{
    $jsonData = json_encode($array);
    $bytes = file_put_contents($file, $jsonData);

    return ($bytes != 0) ? true : false;
}

Now this code is falsy by default, but we will fix it in a moment, in the next section.

Make sure everything is saved and open up your browser to the script we just created. I will use Google Chrome for this demo, but any browser will do.

Now let's take a moment to understand how the debugging process is initialized. Our current status is: Xdebug enabled as Zend extension, listening on port 9000 for a cookie to appear during a request. This cookie will carry an IDE key which should be the same as the one we set-up inside of our IDE. As Xdebug sees the cookie carrying the request, it will try to connect to a proxy, our IDE.

So how do we get that cookie in place? PHP's setcookie? No. Although there are multiple ways, even some to get this working without a cookie, we will use a little browser extension as a helper.

Install the "Xdebug helper"" to your Google Chrome browser or search for any extension that will do it for the browser you are using.

Once you've installed the extension, right click the little bug appearing in your address bar and go to the options. Configure the value for the IDE key to match the key you chose in your IDE, like so:

xdebug-browser-extension

After configuring it, click the bug and select "Debug" from the list. The bug should turn green:

xdebug-browser-extension-active

Now, go back to PhpStorm or your IDE of choice and set a "breakpoint". Breakpoints are like markers on a line which tell the debugger to halt the execution of the script at that breakpoint.

In PhpStorm, you can simply add breakpoints by clicking the space next to the line numbers on the left hand side:

phpstorm-breakpoint

Just try to click where the red dot appears on the screenshot. You will then have a breakpoint set at which your script should pause.

Note: You can have multiple breakpoints in as many files as you'd like.

Now we are all set. Go back to your browser, make sure the bug is green and just reload the page to submit the cookie with the next request.

Tip: if you set a cookie, it will be available to the next request.

If everything goes according to plan, this window should pop up inside of PhpStorm to inform you of an incoming debug connection:

phpstorm-incoming-debug-connection

Did the window not popup for you? Let's do some troubleshooting and repeat what needs to be set in order for this to succeed:

  1. You should find Xdebug info inside of phpinfo()'s output. If not, get the xdebug.so file in the right place and set up your php.ini file.
  2. Set PhpStorm DBGp settings to your IDE key e.g. "PHPSTORM" and port "9000".
  3. Make PhpStorm listen for incoming debug connections using the red phone icon which will then turn green.
  4. Set a breakpoint in your code, or select "Run \ Break at first line in PHP scripts" to be independent from any breakpoints. Note that this is not suited for practical use.
  5. Get a browser extension to set the Xdebug cookie.
  6. Make sure the browser extension has the same IDE key in it that you chose inside of your IDE.
  7. Reload the page and PhpStorm should get the connection.

If you get the dialog seen on the previous image, please accept it. This will take you into debug mode, like so:

phpstorm-in-debug-mode

You can see that the debugger stopped the script's execution at your breakpoint, highlighting the line in blue. PHP is now waiting and controlled by Xdebug, which is being steered by your very own hands from now on.

Our main workspace will be the lower section of the IDE which is already showing some information about the running script (the superglobals).

phpstorm-debugger-vars

And would you look at that? There's the cookie we just set to start the debugging session. You can now click through the superglobals and inspect their values at this very moment. PHP is waiting, there is no time limit, at least not the default 30 seconds.

On the left side, you'll see a few buttons. For now, only "Play" and "Stop" are of interest to us. The green play button will resume the script. If there is another breakpoint in the code, the script will continue until it reaches the breakpoint and halt again.

The red stop button aborts the script. Just like PHP's exit or die would do.

phpstorm-debugger-play-stop

Now the really interesting ones come in the upper section of the debug window:

phpstorm-debugger-controls

Let's quickly check them out:

  1. Step Over: This means step one line ahead.
  2. Step Into: If the blue line highlights, for example, a function call, this button let's you step through the insights of the function.
  3. Step Out: If you stepped into a function and want to get out before the end is reached, just step out.
  4. Run to cursor: Let's say that, for example, your file is 100 lines long and your breakpoint was set at line two in order to inspect something. Now you want to quickly run to the point where you just clicked your cursor to - this button is for you. You can click "Step over" n times too ;)

Now don't worry, as you use Xdebug you will rapidly adapt to the shortcuts on the keyboard.


Actually Debugging Some Example Code

I already told you that the code you copy/pasted is falsy, so you'll need to debug it. Start stepping over the code, statement by statement.

Note that the blue line only halts on lines which actually contain a command. Whitespace and comments will be skipped.

Once you reach the function call to loadData, please do not step into it, just step over and halt on the if statement.

phpstorm-debugger-you-cant-go-back

You can see two new variables in the "Variables" panel on the bottom of the screen. Now, why did the $data variable return false? It seems like the script should have done its job. Let's take a look. Go back to line seven to step into the function call -> bam! We get a message informing us that we can not "step back". In order to get your debugger to line seven again, you need to stop this session and reload the page in the browser. Do so and step into the function call this time.

Stop on the return statement inside of the loadData function and see what happened:

phpstorm-debugger-in-a-function

The $phpData array is empty. The return statement uses a ternary operator to detect what to return. And it will return false for an empty array.

Fix the line to say:

return $phpData;

As json_decode will either return the data or null on failure. Now stop the debug session, reload your browser, and step over the function call this time.

phpstorm-debugger-still-falsy-data

Now it seems like we still have a problem as we step into the condition. Please fix the condition to use is_null() to detect what's going on:

if (is_null($data)) {
    die('Could not load data');
}

Now it's up to you to try and step around a bit. I would suggest to revert the script to the original falsy version, debug it with echo's and then compare how that feels in comparison to using Xdebug.


Conclusion

Throughout this article you should have gained a lot of new knowledge. Don't hesitate to read it again and to help a friend set up Xdebug - nothing better than that!

You may want to try replacing your usual debug behavior by using Xdebug instead. Especially with larger, object-oriented projects, as they become much easier to debug and even catch up on the flow, if you don't get something right away.

Note that this is just the tip of the iceberg. Xdebug offers much more power which needs to be explored as well.

Please feel free to ask any questions in the comments and let me know what you think.

Advertisement