Join our mailing list Subscribe Us

Debug with VSCode, Xdebug, and Docker on Windows

 Creating a PHP app to debug

A simple .php file

We start by creating a new local directory on our computer, that we open in VSCode (File -> Open Folder…). Then we create an index.php file in this directory. It will contain the said code to debug :

<?php
$name = $_REQUEST['name'];
$hello = "Bonjour " . $name . " !";
echo $hello;
This piece of code simply get the name parameter value (given in the URL, and displays it, preceded by Bonjour (yes, I’m French). This will allow us to see a fairly good amount of the variables that the debugger is able to show us.

The docker image

We have to create a new docker image to install Xdebug into. For that, we create a new file called dockerfile (no extension for this one) containing the following commands :

FROM php:7.4-apache # Install XdebugRUN pecl install -f xdebugRUN echo "zend_extension=$(find /usr/local/lib/php/extensions/ -name xdebug.so)" > /usr/local/etc/php/conf.d/xdebug.ini;
To ease things out, we based our image on the official php-apache, which embeds a pre-configured apache server in addition to the last version of PHP. After that, we use PECL to install Xdebug, and finally, we create a .ini file with the command line that goes well to activate Xdebug in the container.

Docker Compose

Still in the same directory, we create a new file named docker-compose.yml. Inside, we write the instructions to launch the container we just described :

version: '3.5' services: # Our application to debug app: container_name: tuto-xdebug build: context: ./ dockerfile: ./dockerfile ports: - '80:80' volumes: - './index.php:/var/www/html/index.php' environment: XDEBUG_CONFIG: remote_host=host.docker.internal remote_port=9000 remote_enable=1

Lines 6 to 10
Here we have only one service called app, which builds a container named tuto-xdebug from the dockerfile file located at the root of the current directory

Lines 11 to 14
The port number 80 of this container is exposed on the port number 80 of our computer. Then we create a link between our index.php file and the index.php file in the directory /var/www/html of the container. (That’s this directory the embedded apache server points to.)

Lines 15 to 16
Finally, we configure Xdebug. Docker provides a specific DNS address host.docker.internal to connect to its host.

Indeed, we could neither use localhost, because our VSCode instance is outside of docker, nor the IP address of our computer, because it changes depending on the network it’s connected to. I point out that it is Xdebug that connects to VSCode, not the other way around. By conventions, we connect Xdebug to port number 9000, but it's completely free, and if we wanted to spy on many containers simultaneously, we would have to distribute them on different ports.

Launching the container

Here we are, our application is ready. Let’s launch it! For that we need a terminal. We place ourselves at the root of our directory, and we type in the magic docker command : docker-compose up

We can now check that our small application works as expected by opening our favorite browser and going to the URL localhost?name=Julien.

We can notice that it works well and displays “Bonjour Julien !”

The VSCode extension

VSCode is a simple text editor. It’s the multiple available extensions that give it all its power and almost makes a complete IDE out of it. The one that interests us today enables us to place breakpoints in the code (even conditional breakpoints!), execute instructions step by step, explore variables… In short, everything we have the right to demand from a good debugger. It’s called PHP Debug and it’s edited by Felix Becker.

Installation of the extension

To install it in VSCode, we need to :

  • Click on the “Extensions” icon (or Ctrl + Shift + X),
  • then use the search field to find the extension (type xdebug),
  • and finally install it (click on the little green “Install” button).
The PHP extension by Felix Becker

To work, you need PHP installed on your computer. We can check it’s installed by typing the php -version command in a console. It should output the currently installed version of PHP.

If not, you can download the last version (nonthread safe) on this site.

Configuration of a listener

The PHP Debug extension is able to manage many configurations if we need to listen to many instances of Xdebug. We are going to create only one here, for our container.

For that we need to :

  • Click on the “debug” icon on the sidebar (or Ctrl + Shift + D),
  • then click on the gear icon titled “Open launch.json” next to the drop-down menu at the top of the left side panel,
  • then click on PHP in the list of proposed environments. A hidden folder named .vscode containing a file named launch.json is created in our project root folder.

We can easily deduce that the configuration of our extension is liked to our project, which is convenient because all of our projects don’t necessarily use Docker and will likely have different configurations.

Inside our launch.json file, we notice 2 configurations that we can delete (or not, doesn’t matter). We are gonna create a new one :

{
"name": "Listen for XDebug on Docker App",
"type": "php",
"request": "launch",
"port": 9000,
"pathMappings": {
"/var/www/html": "${workspaceFolder}"
},
"hostname": "localhost",
"xdebugSettings": {
"max_data": 65535,
"show_hidden": 1,
"max_children": 100,
"max_depth": 5
}
},

Line 5
We shall pay particular attention to the port number we use. It must identical to the one we gave in our docker-compose.yml file.

Line 6
The pathMapping must also be right. Do not use an absolute or relative path. We must use the variables VSCode provides us.

Line 9 ### Added on 11/11/2020
The hostname must be set to localhost. This is especially necessary when working with WSL2.

Lines 10 to 15
The xdebugSettings have been copied from the internet and can (must ?) be adjusted to our needs.

Now place to magic! Let’s actually see our code execute!

Let’s debug

Now that everything is ready, we're gonna be able to place our very first break-point!

For that, we need to open the index.php file (in VSCode) and place our pointer on the left of the line number before which we want to pause the execution. A dark red dot appears. If we click it becomes bright red and stays in place. The break-point is placed.

In the left side panel, we make sure that our configuration is the one selected in the drop-down menu, then we click on the little green triangle titled “Start debugging”.

A new menu bar containing 6 buttons appears on the top-right corner of VSCode. It is now ready to intercept the next execution.

So finally we refresh our web page in the browser (localhost?name=Julien) and Tadaa! The execution stops on our break-point!

VSCode is highlighting the line on which the execution paused. In the left side panel, we can browse through the global and local variables and see their values.

We can also see the values of our local variables by hovering them directly in the code. The new menu bar on the top-right corner allows us to execute lines of code one by one, to stop the execution, or to release it and let it continue (to the next break-point!).