Getting to Know and Love Xdebug

Bruno Skvorc

It’s been 15 years since Xdebug first came out. We think this is the perfect opportunity to re-introduce it to the world, and explain how and why it does what it does.

Xdebug alternative logo

Xdebug is a PHP extension (meaning it needs to be compiled and installed into a PHP installation) which provides the developer with some features for debugging. Those include:

  • stack traces – detailed output of the path the application took to reach a given error, including parameters passed to functions, in order to easily track the error down.
  • a prettier var_dump output which produces color coded information and structured views, similar to VarDumper, along with a a super-globals dumper
  • a profiler for finding out where the bottlenecks in your code are, and the ability to visualize those performance graphs in external tools. What this results in is a graph similar to that which Blackfire produces.
  • a remote debugger which can be used to remotely connect Xdebug with running code and an end-client like an IDE or a browser to step through breakpoints in code and execute line by line of your application.
  • code coverage which tells you how much of your code was executed during a request. This is almost exclusively meant to help with unit tests and finding out how much of your code is test-covered.

How do I use it?

Xdebug comes with a detailed installation page which handles most if not all use cases, but if you’d like to play with the functionality presented below, we recommend using Homestead Improved which comes with the extension pre-installed and activated.

With modern IDEs and Blackfire, is there even a need for Xdebug?

IDEs do provide good code lookup functionality, so the link format functionality’s usefulness can seem questionable. There’s also loggers of all kinds now which can handle errors and exceptions. Likewise, function traces and profiling are done really well in Blackfire. However, file link formats are just one part of Xdebug, and using Blackfire has its own hurdles – installing the extension, setting up the keys, and then paying to keep trace history. Loggers also need to be used with a lot of foresight, and aren’t very easy to add into an application later on.

There’s more to Xdebug than just this, though – it’s still required for proper unit testing (testing frameworks depend on it for code coverage reports), it’s far from easy to get remote break-point debugging going via other means, and it’s a tool so old and stable it’s been ironed out to near perfection.

If your current tools can handle everything it offers or you don’t need the features it offers then of course, there’s no need for Xdebug, but I’ve yet to start a single project that could be completed just as efficiently without it.

Let’s Try It Out

I’ll assume you have a working Xdebug installation at this point. If not, please consider using Homestead Improved.

Let’s make a new project folder with a simple index.php file, and echo out a non-existent variable like $foo:


echo $foo;

This is what we get:

Xdebug styled error

Turning Xdebug Off

Screens like these are so ubiquitous these days, and such a common default, that most people don’t even realize this is already Xdebug-styled. To prove it, let’s see how it looks without Xdebug. To disable Xdebug, we edit the file /etc/php/7.1/fpm/conf.d/20-xdebug.ini in Homestead Improved, and comment out the first line:

xdebug.remote_enable = 1
xdebug.remote_connect_back = 1
xdebug.remote_port = 9000
xdebug.max_nesting_level = 512

We need to restart PHP-FPM afterwards:

sudo service php7.1-fpm restart

Note: if you’re using another development environment with a different PHP installation, your Xdebug ini file might be elsewhere. Consult your system’s documentation for the exact location, or ask in the comments below.

Bare PHP error

Looks quite barren, doesn’t it? It’s missing the whole call stack. Granted, this information isn’t particularly useful at this point since we’re only dealing with a single line in a single file, but we’ll look at a heavier use later on.

Reactivate Xdebug now by removing the comment in the previously edited file, and let’s continue. Don’t forget to restart PHP!

File Clickthroughs

If you’re a developer who’s fixed on an IDE (like I am on PhpStorm), it would definitely be useful to be able to click on files in the stack trace and go directly to them in the IDE. A non-trivial upgrade in debugging speed, for sure. I’ll demonstrate the implementation of this feature for PhpStorm.

First, let’s open the 20-xdebug.ini file we edited previously, and add the following to it:

xdebug.file_link_format = phpstorm://open?%f:%l

Note that this will work in some browsers, and won’t in others. For example, Opera has problems with phpstorm:// links and will gladly crash, while Firefox and Chrome work just fine.

If we refresh our invalid PHP page now, we’ll get clickable links which open the IDE at the precise location of the error:

Xdebug allows clicking through to the IDE

The process is the same for other IDEs and editors.

Xdebug with Vagrant and PhpStorm

Why stop at this, though? Many people today develop on virtual machines, making sure no part of the PHP runtime ever touches their main machine, keeping everything fast and smooth. How does Xdebug behave in those cases? Additionally, is it even possible to do break-point debugging where you step through your code and inspect each line separately when using such complex environments?

Luckily, Xdebug supports break-point-powered remote connections perfectly. We’ve covered the process before, so for a full gif-powered setup tutorial, please follow this guide.

Using the Profiler

As a final quick tip, let’s inspect one of the often neglected features: the profiler. For that, we’ll need a heavy application like Laravel.

composer create-project --prefer-dist laravel/laravel xdebug

Once again, we need to edit the 20-xdebug.ini file, and add the following:

xdebug.profiler_enable_trigger = 1
xdebug.profiler_output_dir = /home/vagrant/Code/

Note that we’re not using xdebug.profiler_enable = 1 because we don’t want it to stay on 100% of the time. Instead, we’ll use the trigger query param “XDEBUG_PROFILE” to selectively activate it. We’re also outputting the cachegrind profile into the main shared folder of our VM so that we can inspect it with tools on the host operating system.

After restarting PHP, we can try it out by executing (replace with whichever vhost you picked, or the VM’s IP). Sure enough, the file is there:

Cachegrind file in the main folder

Every OS will have its own cachegrind inspector tool, and on OS X one of those is qcachegrind, easily installed via Homebrew. Refer to your OS’s preferred visualizer for installation instructions. After installing it…

 brew install qcachegrind --with-graphviz

… and opening the file in the viewer, we can see a nice breakdown of the execution flow:

Cachegrind explored

The profiler offers and immeasurable wealth of data and truly deep insight into the way your code behaves, just like Blackfire. With the profiler’s local output, however, it’s easier than ever to automate the continuous tracking of performance and execution complexity.

Forcing Xdebug’s Render on Laravel

By default, Laravel has custom error reports and rendering set up. An error like the one we caused before with an undefined variable would, in Laravel, look like this:


use Illuminate\Http\Request;

Route::get('/', function(Request $request){
    echo $foo;
    return view('welcome');

An error of an undefined variable in Laravel

While Symfony’s error screen (which is what Laravel is using here) is configured to also play nice with Xdebug’s clickthrough (try it, you can now click on these files and their lines, too!), I really miss the memory output (Xdebug by default also outputs the memory usage at every point in time in the stacktrace). Let’s revert this to Xdebug’s screen while in development mode, so we can inspect that attribute.

use Illuminate\Http\Request;

Route::get('/', function(Request $request){
    ini_set('display_errors', 1);
    echo $foo;
    return view('welcome');

You can see here we updated our default route so that it first activates the displaying of errors (the screen we saw earlier is not a shown error per-se, but a caught exception the stack trace of which was manually extracted and rendered), and then we restore the error handler to its default value, overriding Laravel’s.

After refreshing, sure enough, our old screen is back – just look at that stack trace tower and memory consumption!

Laravel's Xdebug call stack

I encourage you to investigate further on your own – look around in the docs, play with the options, see what you can find out about your applications.


Xdebug is a valuable tool for any developer’s toolbelt. It’s a powerful extension that fully lives up to the word, extending the language we work in daily to be more verbose, more user friendly, and less mysterious when errors appear.

With 15 whole years behind it, Xdebug has set a high standard for debugging tools. I’d like to thank Derick for developing and maintaining it all this time, and I’d love it if you chose to write a tutorial or two about in-depth usage, caveats, or secret feature combinations no one’s thought of before. Let’s spread the word and help it thrive for another 15 years.

Happy birthday, Xdebug!