How to Use Xdebug for Advanced PHP Debugging

You could just debug your PHP code using functions such as error_log, print, and var_dump, (and to be honest we’ve all done it, a lot!), but sometimes they just aren’t enough, and can actually slow you down while developing.

There must be a better way, surely?! Enter Xdebug, the rather awesome debugging and profiling tool for PHP.

In this post, I’ll take you through why Xdebug is amazing, getting it setup, how to use it, get the most out of it it, and some neat advanced uses all to make your life easier.

You can thank me later (in the comments) 🙂

How Xdebug Changed My Life

A few years ago I didn’t know debuggers existed, had never heard of Xdebug, and was quite happy using a function I’d cobbled together pinched from Stack Overflow in all my WordPress sites for debugging:

if ( ! function_exists( '_log' ) ) {
    function _log( $message ) {
        if ( WP_DEBUG === true ) {
            if ( is_array( $message ) || is_object( $message ) ) {
                error_log( print_r( $message, true ) );
            } else {
                error_log( $message );
            }
        }
    }
}

It was all I thought I needed until someone introduced me to Xdebug and my life changed. Excuse the hyperbole, but it happened!

When you are debugging using functions like error_log, you are only outputting the variables you define, which might not be what you need. For example, while troubleshooting some code, you add the function somewhere to debug a variable. You refresh your page and/or repeat the action so the debug line is executed, and then check your error log to look at the output. If this doesn’t give you any insight to the cause of the problem at hand, then you need to go back and add more debug lines to the code. Rinse and repeat.

Xdebug allows you to break during code execution and inspect all the variables in scope during a request. This means you have all you need to troubleshoot during only one iteration of this cycle. This can save a huge amount of time when tracking down issues, and helps make your development workflow more efficient.

Troubleshooting++ with Xdebug

Xdebug really shines when trying to troubleshoot a problem whose cause is completely unknown to you. If you know your function is broken, logging lines inside it is great and works well. But strange problem-causing behavior could be coming from anywhere in a project, like a plugin or WordPress core itself. How the hell do you track that down?

Xdebug’s breakpoints allow you to pause code at any point, which means the best thing to do is to break early and follow the code execution through with Xdebug (more on that later) until you spot something gone awry. To save time, you can progressively move breakpoints further along in the execution until you get closer to the potential issue, instead of breaking on code already analyzed as not the cause of the issue.

When you find the issue, you can actually change the values of variables on the fly after they have been assigned, so you can test potential fixes to the issue while debugging in the same request.

Debug Driven Development

Another great use of Xdebug is proactively during development itself, instead of reacting to issues with existing code. Think of this like Test Driven Development (TDD) but using the debugger first instead writing tests. I’m not even sure if Debug Driven Development (DDD) is a widely used term, but for me, I’ve used Xdebug to help me write new code in a couple of ways:

  1. Inspect existing arrays, objects, and class instances to find the data available to me in code, so I can use it in new code I write
  2. Immediately debug a newly-written piece of code to test it is working as expected

This isn’t something I do all the time, but Xdebug gives me this insight when I need it.

Installing Xdebug

Hopefully the benefits I’ve detailed have got you wanting to use Xdebug and you’re ready for some installation steps.

I’ve put together a list of the most common local environments with some handy links to getting Xdebug installed on them:

Do you work with a different local environment and need some guidance? Let us know in the comments.

PhpStorm Integration

News flash: I love PhpStorm! And guess what? PhpStorm has an awesome integration with Xdebug and is simple to set up. Other editors and Integrated Development Environments (IDEs) with Xdebug support are available.

Start Debugging

Once you’ve installed Xdebug and configured PhpStorm, you can start interactively debugging your code. This is done by setting breakpoints at certain lines of your code and telling PhpStorm to listen for incoming connections. When PHP executes a line that has a breakpoint on it, Xdebug will step in and pause execution allowing you to gain valuable insight to all that is happening during execution:

PhpStorm Xdebug debugging panel

The sidebar of the debug panel has various buttons for controlling the execution of the code. Here they are top down as they appear in the screenshot:

  • Resume Program – continue with the PHP execution
  • Pause Program – not available during debugging
  • Stop – halt the execution
  • View Breakpoints – bring up a window of all the breakpoints set in all files of the project
  • Mute Breakpoints – deactivate the breakpoints during execution (good for just finishing the request without breaking any further)
  • Restore Layout – resets the default view of the Debug panel
  • Settings – tweak the debugger display
  • Pin Tab – always show the Debug panel
  • Close – close the panel and choose to disconnect the debugger from the connection or terminate it completely
  • Help – links to JetBrain’s online help docs for the Debug panel

The top bar of the panel controls how the debugger traverses the code so you can inspect different parts of the codebase:

  • Show Execution Point – jumps back to where the program is broken
  • Step Over – execute and move to the next line of the file
  • Step Into – if the next line has one or more functions, move the debugger into those to step through
  • Force Step Into – step into a function that is marked as skipped
  • Step Out – move the debugger out of the current function back to the function that called it
  • Run to Cursor – execute all the way to the line the cursor is on
  • Evaluate Expression – execute PHP while the debugger is running (think of this like Chrome’s JS console)
  • Show Values Addresses – displays the memory address of objects
  • Hide Empty Superglobals Variables – removes some noise from the variables list
  • Add Method to Skip List – mark method to be skipped next time

Breakpoints are added by clicking in the left-hand gutter of the code, along the line you want at which you want to break. PhpStorm also allows you to set conditional breakpoints, where you add PHP logic to control when a breakpoint actually fires:

PhpStorm add conditional breakpoint

When troubleshooting, it is often helpful to inspect and watch the value of a variable all the way through the execution of a request to see when it changes. PhpStorm allows you to add variables to a list that it watches and displays in a separate ‘Watches’ panel, away from all the noise of the main ‘Variables’ panel. You can watch a variable by either right-clicking the variable in the file during debugging and selecting ‘Add to Watches’ or doing the same from the variable in the ‘Variables’ panel:

PhpStorm add variable to watch list

Stack Trace

Enabling Xdebug gives you, by default, an extended stack trace for any errors, notices or warnings that are written to the PHP log:

[26-Jul-2017 16:06:21 UTC] PHP Notice:  Trying to get property of non-object in /Applications/MAMP/htdocs/deliciousbrains.com/public_html/content/themes/deliciousbrains/functions.php on line 302
[26-Jul-2017 16:06:21 UTC] PHP Stack trace:
[26-Jul-2017 16:06:21 UTC] PHP   1. {main}() /Applications/MAMP/htdocs/deliciousbrains.com/public_html/index.php:0
[26-Jul-2017 16:06:21 UTC] PHP   2. require() /Applications/MAMP/htdocs/deliciousbrains.com/public_html/index.php:3
[26-Jul-2017 16:06:21 UTC] PHP   3. require_once() /Applications/MAMP/htdocs/deliciousbrains.com/public_html/wp/wp-blog-header.php:19
[26-Jul-2017 16:06:21 UTC] PHP   4. include() /Applications/MAMP/htdocs/deliciousbrains.com/public_html/wp/wp-includes/template-loader.php:74
[26-Jul-2017 16:06:21 UTC] PHP   5. get_header($name = *uninitialized*) /Applications/MAMP/htdocs/deliciousbrains.com/public_html/content/themes/deliciousbrains/404.php:2
[26-Jul-2017 16:06:21 UTC] PHP   6. locate_template($template_names = *uninitialized*, $load = *uninitialized*, $require_once = *uninitialized*) /Applications/MAMP/htdocs/deliciousbrains.com/public_html/wp/wp-includes/general-template.php:45
[26-Jul-2017 16:06:21 UTC] PHP   7. load_template($_template_file = *uninitialized*, $require_once = *uninitialized*) /Applications/MAMP/htdocs/deliciousbrains.com/public_html/wp/wp-includes/template.php:647
[26-Jul-2017 16:06:21 UTC] PHP   8. require_once() /Applications/MAMP/htdocs/deliciousbrains.com/public_html/wp/wp-includes/template.php:688
[26-Jul-2017 16:06:21 UTC] PHP   9. do_action($tag = *uninitialized*, $arg = *uninitialized*) /Applications/MAMP/htdocs/deliciousbrains.com/public_html/content/themes/deliciousbrains/header.php:27
[26-Jul-2017 16:06:21 UTC] PHP  10. WP_Hook->do_action($args = *uninitialized*) /Applications/MAMP/htdocs/deliciousbrains.com/public_html/wp/wp-includes/plugin.php:453
[26-Jul-2017 16:06:21 UTC] PHP  11. WP_Hook->apply_filters($value = *uninitialized*, $args = *uninitialized*) /Applications/MAMP/htdocs/deliciousbrains.com/public_html/wp/wp-includes/class-wp-hook.php:323
[26-Jul-2017 16:06:21 UTC] PHP  12. dbi_theme_body(*uninitialized*) /Applications/MAMP/htdocs/deliciousbrains.com/public_html/wp/wp-includes/class-wp-hook.php:298
[26-Jul-2017 16:06:21 UTC] PHP  13. dbi_theme_render_part($part = *uninitialized*, $args = *uninitialized*) /Applications/MAMP/htdocs/deliciousbrains.com/public_html/content/themes/deliciousbrains/functions.php:137
[26-Jul-2017 16:06:21 UTC] PHP  14. include() /Applications/MAMP/htdocs/deliciousbrains.com/public_html/content/themes/deliciousbrains/functions.php:11
[26-Jul-2017 16:06:21 UTC] PHP  15. dbi_theme_is_products_page() /Applications/MAMP/htdocs/deliciousbrains.com/public_html/content/themes/deliciousbrains/parts/nav/nav.php:15
[26-Jul-2017 16:06:21 UTC] PHP  16. dbi_theme_is_product_page($product = *uninitialized*) /Applications/MAMP/htdocs/deliciousbrains.com/public_html/content/themes/deliciousbrains/functions.php:315

The extended stack trace is helpful on its own, but, coupled with PhpStorm, you get a dynamic view of the trace when debugging. This dynamic view gives you an interactive history of what code has been executed up to the breakpoint or current point of the execution:

PhpStorm Xdebug dynamic stack trace

Because I rely on wp-content/debug.log or the PHP error_log to alert me to issues, but I use PhpStorm to see the stack trace, I end up disabling the extended (and very noisy) stack trace in the log by putting this in my wp-config.php file:

if ( function_exists( 'xdebug_disable' ) ) {
    xdebug_disable();
}

Advanced Use

Mastered the basics? Ready for more? Here are some advanced ways you can use Xdebug to improve your development workflows.

Profiling

When you really need to investigate performance issues with your code or website, one of the best tools we’ve found is Blackfire, which we’ve written about before. But Xdebug also profiles for you and PhpStorm can interpret the results, which is great as it means you never have to leave PhpStorm! 😂

To enable the profiler, simply edit your php.ini file to add or uncomment these lines in the Xdebug section:

xdebug.profiler_enable=1
xdebug.profiler_output_dir="/Applications/MAMP/tmp"

Then Xdebug will create a profile log file for each execution of your code. Remember to disable the profiler when you’re done so you don’t fill your hard drive with log files! Alternatively set the output directory to the /tmp directory for your OS.

These logs can then be analyzed in PhpStorm by navigating to Tools > Analyze Xdebug Profiler Snapshot and selecting the log file. The visualization isn’t as in-depth as Blackfire or using something like KCachegrind but it is still is a great way to analyze bottlenecks in your code:

Analyzing Xdebug profile log in PhpStorm

Remote Debugging via SSH

You can troubleshoot bugs only if you can recreate them on your development environment. Of course when it comes to running a website, we recommend having your development environment as close to production as possible, and you debug on a near-as-possible copy of the production database, but sometimes bugs and oddities will only happen on production. No one wants to start messing around on a live site, editing files to add logging lines. Don’t worry PhpStorm and Xdebug have you covered!

You can install Xdebug on a remote server and debug the code execution locally using Xdebug and PhpStorm. This feature is a lifesaver and suprisingly simple to set up. Just make sure to turn off Xdebug when you are finished, so your site’s performance isn’t impacted by Xdebug unnecessarily.

Conclusion

I hope this has been a good introduction to how powerful and valuable using a debugger like Xdebug is for your development workflow. For me, it is a huge time saver and I couldn’t imagine developing without it in my toolbox.

Do you use Xdebug or another debugger? Have I missed any awesome features or do you have any tips? Let me know in the comments below.

About the Author

Iain Poulson

Iain is a WordPress and PHP developer from England. He builds free and premium plugins, as well as occasionally blogging about WordPress. Moonlights as a PhpStorm evangelist.

  • Nocare

    I recommend setting xdebug.profiler_output_dir to your OS temporary file directory if using it.
    I found 18gb that had gone missing in the form of profiler logs that were in my installation directory.

  • Arik Waisman

    How would you do this with a docker instance, or really multiple docker instances.

    I have two separate projects that each run their own separate containers using the same docker set up. The only thing I change in order to get them running is the port that they bind too. One of my projects are on localhost and the other localhost:8085. I was able to get xdebug running on one of them by running ‘sudo ifconfig lo0 alias 10.254.254.254’ to create what i believe is a static IP, but can not get it running on the other project. Any insight would be immensely helpful.

    • Hi Arik, I’ve not used Xdebug within a Docker instance myself I’m afraid. Hope you get it resolved 🙂

  • I’m trying to get Xdebug working with Xdebug for Sublime Text 3 but can’t get the integration working. Anyone else use Xdebug for Sublime Text 3?

    • Ian

      Hello! I used to use Sublime but now Atom. You can get it set up with all the functionality just as PhpStorm has but in the prettier Atom GUI. Generally you need to install a Chrome extension to trigger or add a query variable localhost:3000/?XDEBUG_SESSION_START.

      • For the life of me, I cannot get Xdebug to work. Installed and re-installed via Homebrew. Everything checks out in `php –version` and `php -i` when plugged into https://xdebug.org/wizard.php

        Everything checks out except I cannot get Xdebug to show anything in either PhpStorm or Sublime. No information in Frames or Variables in PhpStorm even after Validating Debugger Configuration on Web Server and connecting in Chrome to JetBrains IDE Support

        What’s up? I can set breakpoints all over and break php functions and I get no information about anything beyond the occasional Javascript error.

        • Ian

          Hey Alex. I’ll double check my set up later or tomorrow. I’m using Brew to run PHP5.6/7/7.1 with relevant Xdebugs. Are you using PHP-FPM?

          I also use this (installed via Brew) to toggle Xdebug on / off.
          https://github.com/w00fz/xdebug-osx

          • Thanks! I actually just got it working. Reading through the debug logs, I saw “Creating socket for ‘::1:9000’, poll success, but error: Operation now in progress (19).” which lead me here :

            http://techqa.info/programming/question/41423139/phpstorm+xdebug-hits-a-breakpoint-only-when-using-external-ip-address

            And turning xdebug.remote_connect_back to 0 (the default value) solved the issue.

            Xdebug documentation:

            If enabled, the xdebug.remote_host setting is ignored and Xdebug will try to connect to the client that made the HTTP request. It checks the $_SERVER[‘HTTP_X_FORWARDED_FOR’] and $_SERVER[‘REMOTE_ADDR’] variables to find out which IP address to use.

            Counterintuitive but there you go…

          • Glad to hear you got it working, Alex!

  • Wanted to ask a dumb question lol

    When you are posting code you’re using

    Do you have a plugin doing that or is there some custom CSS giving you the formating?

    Also, with your solution, does the code get stripped by WP when you switch from Text to the Visual editor?

    Clint

  • Iain, perhaps you should also mention that just having the xdebug module enabled in the interpreter configuration file will slow down script execution by multiples (3-4x on average).

    To overcome this, I set up a separate “debug” PHP-FPM instance and some Apache 2.4 magic to check for XDEBUG_SESSION cookie value in request headers. In case this cookie is set, Apache routes the request to the “debug” instance instead via some clever mod_proxy_fcgi directives.

    With external object caching, like Redis, this actually works out all right. Interpreter instance specific things like APCu, probably not so well.

  • Justin Tucker

    Ian, I appreciate the detail you go into with these xdebug posts. When I was first learning how to set everything up, resources were sparse and not thorough. I learn something new each time. Keep it up.

  • lyles63456

    In this time there are more users are have more eager about PHP Debugging tools and they find more helps from here. I think it will be so more better for us that we find more information also from here.

  • Wen Shenk

    Can I use xdebug without having to download the whole project to local? My website is hosted on my own ubuntu server, I usually download the PHP file I want to debug, modify it using Atom and upload it back to server to see the change(I use Fireftp to make the downloading,editing,uploading a lot smoother)