Understanding Xdebug’s remote debugging with PHPStorm and Docker

Xdebug is a well known and very handy PHP extension that has three main uses:

  • Add a stack trace to PHP’s standard error messages, giving quite a lot more context to the error,
  • format the output of var_dump() which is really nice with arrays,
  • and get out of the “var_dump() exit;” debug scheme tanks to remote debugging and the use of its step-by-step debugger via your text editor/IDE.

The two first steps require no configuration except from installing and loading xDebug.

Remote debugging, on the other hand require a little more config, which depends on your setup, which actually varies a lot when you run PHP from a docker container…

There is a lot of articles on Xdebug, Docker and PHPStorm but pretty much all of them takes its particular setup for granted, most presents a different set of Xdebug configuration -not realizing that some of them are useless for their particular use case- and especially none of them explain why you have to do things this way.

I aim to do better in this article, and hopefully will provide a good overview of the most common Xdebug configurations, when and why to use them.

Please note also that this article is written in march 2018 and for all the examples below uses PHP 7.0, Xdebug 2.6 and PHPStorm 2017.3.

Basic Xdebug goodies

If I run this simple code…

<?php
function test() 
{
	echo [];
}
test();

var_dump([
	"key" => "value",
	"key2" => [
		"key" => 123
	]
]);

… without Xdebug enabled, it will just output in the browser these two lines (without colors):

<b>Notice</b>: Array to string conversion in <b>/var/www/xdebug/index.php</b> on line <b>5</b>
Arrayarray(2) { ["key"]=> string(5) "value" ["key2"]=> array(1) { ["key"]=&gt; int(123) } }

With more complex scripts and array, the error may be completely useless on its own and the array will most certaily be unreadable. Note that in the CLI, the array is already formatted.

But when Xdebug is enabled, it will look like this:

The error message doesn’t change but it is a lot more visible and Xdebug prints you the full stack trace (all the functions that have been called, at which line and in which file) that led to the error.

The array is nicely formatted, making the hierarchies (sub-arrays) and values really clear.

In the CLI, you can activate some coloration by enabling xdebug.cli_color.

Understanding remote debugging

A bug that’s not a syntax error is typically due to a variable not having the correct value. On simple cases, a few well placed var_dump() and exit are enough. But in my opinion, it gets old really fast on bigger projects where you probably would like to monitor several variables at different points in your request’s lifetime without having to write and delete var_dump() and exit statements all the time.

Thankfully, Xdebug comes with a  set of very powerful tools: a step-by-step debugger and the ability to third-parties (text editors and IDEs) to interact with it via the DBGp protocol.

Xdebug is actually not an extension of PHP, but of the Zend Engine. It has very deep ties with how your PHP program runs and that’s what allow it to extract all kind of information about it and to tell it to stop -but not terminate- at any point.

Instead of writing a bunch of var_dump statements, you will be able to tell your IDE to stop the program a any given lines, which allows you to evaluate and even modify on the fly the sate of any variables in the current scope. If needed you can run the whole program one line at a time, observing how its state change throughout.

“All” you need for that to happen is:

  • (Enable Xdebug and) enable remote debugging with xdebug.remote_enable=1,
  • tell Xdebug where to send debug info with xdebug.remote_host and xdebug.remote_port or xdebug.remote_connect_back=1,
  • tell your text editor:
    • on which port the debugging server should listen to,
    • to start said server,
    • on which line(s) you would like your program to stop (set a breakpoint),
    • to setup path mapping, if PHP runs in a container (or any remote environment),
  • and finally start your program in a way that will also start an Xdebug debug session (more on that just later).

This may seems a lot but all configuration is done once and may be unnecessary thanks to their default values, and setting breakpoints is at best a few clicks away.

Setting the remote host and port

During a debugging session, Xdebug and the IDE will communicate back and forth, so Xdebug needs to know where to send its debug information. The default value is localhost:9000.

The important thing to understand is that the remote_host must be the host (IP) of the machine that runs the IDE.

If the host is not localhost, you can either set xdebug.remote_host to the correct value, or set xdebug.remote_connect_back=1.

Remote connect back will make Xdebug … connect back to whatever host connected to PHP. This is particularly useful for me at work for instance where we all use the same docker container config but each have a different local IP.

If for some reason, you can’t use 9000 as the port, just change it in xdebug.remote_port, as well as in PHPStorm’s settings: File > Settings > Languages & Frameworks > PHP > Debug > Debug Port.

Preparing PHPStorm

In PHPStorm go to File > Settings > Languages & Frameworks > PHP > Debug

  • make sure Ignore external connections through unregistered server configuration is unchecked
  • make sure Can accept external connections (just below) is checked

This makes sure PHPStorm allows external connections from Xdebug. As said above, also change the port in the same section if needed.

Then click the Run > Start Listening for PHP Debug Connections menu item (or click on the similar button in the top right). PHPStorm will starts its debugging server, waiting to receive connections fro Xdebug.

Launching a debug session

The last thing to do is to launch the script you want to debug while telling Xdebug to start a debug session.

If you are sure you always want to do that you can just set xdebug.remote_autostart=1 and you are covered. Remember that if needed you can always “turn off debugging” on PHPStorm’s side.

If you want to start a session only when needed, all you need to do is set a XDEBUG_SESSION cookie with any value. You can of course set and delete this cookie manually with the browser’s developer tools, but you probably better use one of the extensions. You can also add XDEBUG_SESSION_START as a GET or POST parameter to set the cookie and XDEBUG_SESSION_STOP to delete it.

On the command line, either run the script with the -d xdebug.remote_autostart=1 option, or first set the XDEBUG_CONFIG environment variable (again, its value don’t matter).

Local debugging without container

From now on I suppose that debugging is “ready”: that the extension and remote debugging are enabled, an Xdebug session is initiated and that PHPStorm listens for it.

When PHP and PHPStorm are on the same machine, the only things left to do is to set a breakpoint by clicking in the sidebar, a red dot will appear and the line  background will be red. To be clear, this works because by default, PHPStorm will start its debug server on localhost:9000, which is precisely where Xdebug will connect to by default.

If you load the page now, PHPStorm should gain focus and present you with the Incoming Connection From Xdebug screen like this one.

There  is nothing more to do here, just click Accept and PHPStorm will present you with the debug pane at the bottom and the line at the first breakpoint highlighted.

Debugging from a Docker container

Now it gets slightly more tricky, depending on how the container is built.

In my setup, the files of my application and PHPStorm are on my host machine and I run a single container with PHP + Xdebug + Nginx. A volume maps the files from the host machine to the container.

The obvious change to make to the config is the remote host that must not be localhost anymore but must point to the host machine, except if you set xdebug.remote_connect_back=1. In that case, Xdebug will find the correct host to connect to, whatever the value of xdebug.remote_host is.

The next issue is path mapping because it is likely that the path of the file in the container is not the same as in the host machine. Thankfully, PHPStorm should be smart enough to figure that out on its own.

When you load the page for the first time, you should have the same Incoming Connection From Xdebug screen as before. Again, you should be able to just click Accept.

The server name is empty

There is a particular case where PHPStorm will receive the connection from Xdebug, display the initial window and correctly setup the path mapping but it will not work: when the server name is empty.

The value of first field of the initial window (server name) is taken from the first value of the server_name directive from the Nginx’s virtual host that serve the file. This directive is however optional and indeed not necessary in the case of containers because they typically only contains a single vhost which has no reason not to be set as the default one.

Note that server name will also be empty when the script is run on the CLI.

But PHPStorm uses this value to link a server (and its path mapping) to an incoming connection, so it seems to bug out when it is empty.

The solution to that is to set a server name via the PHP_IDE_CONFIG environment variable, like so: PHP_IDE_CONFIG="serverName=the_server_name". The actual name of the server don’t matter but it must not be empty.

Once this is set and a debug session initiated, PHPStorm will not take focus, and debug will not work yet but the debug panel will warn that it Can’t find a source position. Server with name ‘the_server_name’ doesn’t exist..

In that case you just need to create the server and path mapping manually. Go to File > Settings > Frameworks & Languages > PHP > Servers. Fill the server name field, check the Use path mappings box and set the Absolute path on the server side accordingly. You can leave the host field empty.

It should be all good now.

But what about the IDE key and the DBGp proxy ?

The only case where the IDE key is useful is to distinguish developers on a DBGp proxy.

It was useful when several developers were debugging the same files on the same server and when the remote_connect_back setting didn’t existed. Since you can only set a single host as the remote host, there was no way for Xdebug to distinguish individual developers and to know where to connect to.

The solution was to setup a DBGp proxy, typically on the same development server and to point Xdebug to it.

The developers would then have to setup an IDE key through their IDE and connect to the proxy. When initiating a debug session, they would attach their IDE key to it as the value of the cookie. The IDE key would have to be unique, so typically not the IDE name as we often see.

This is how the proxy would know to which dev redirect which connection from Xdebug.

This solution has mostly been rendered obsolete by the introduction of the handy remote_connect_back setting introduced in Xdebug 2.1.

Conclusion

Once you understand what’s going on, configuring Xdebug and your IDE is pretty simple and rather obvious.

If an IDE is too much for you and you prefer the lightweight SublimeText (or Atom, no one is jugging you), it has got you covered too. Its Xdebug Client package is equally easy to configure, but the integration of a debug session to the editor’s UI is in my opinion really inferior with PHPStorm.

Xdebug’s usefulness goes beyond debugging since it can team-up with PHPUnit for instance to provide code coverage reports and also profile your code, although blackfire.io and its very nice UI is worth checking out.

There are a lot more settings dedicated to Xdebug and remote debugging, but if I did’t talked about, it’s that you most probably don’t need to change its default value.

For more information about debugging in PHPStorm: https://www.jetbrains.com/phpstorm/documentation/debugging/

Comments are closed