Xdebug in VSCode with Docker
Tags: php, xdebug, vscode, docker
Published: 2021-12-10 04:08:09

In my last post, I talked about how to configure a [development](https://dev.to/jackmiras/docker-compose-for-a-laravel-app-ie7) environment and how it extends a Dockerfile made for production.

Now, I would like to share how we can build upon our previous Dockerfile in such a way that Xdebug can run directly from Docker and also connect it with Visual Studio Code.

By choosing this approach, we substantially reduce the amount of setup that each team member has to do on their machine to get the project up and running, which means that we can start writing code faster.

So, why is this so important? Recent [research from JetBrains](https://www.jetbrains.com/lp/devecosystem-2022/php/#how-do-you-usually-debug-php-code-) shows that 62% of PHP developers debug their code using var_dump(), die(), dd(), and dump(). From my perspective, there is nothing wrong with that. Even if you do it by choice and not because you lack knowledge.

I'm included in the 62% of developers who debug their code with auxiliary functions instead of using a full-featured debug solution such as Xdebug. I'm a heavy Neovim user, and I didn't adapt quite well to using Neovim with Xdebug; it is just easier and faster to use my code snippets around the dd() function.

But occasionally, I catch myself in situations where it would be faster to jump into Visual Studio Code and just use Xdebug, especially when I'm working with other people who aren't familiar with Vim or Neovim.


### Content
- [XDebug config file](#xdebug-config-file)
    - [Explaining xdebug.ini](#explaining-xdebug.ini)
- [Visual Studio Code](vscode)
    - [Explaining launch.json](#explaining-launch.json)

### Xdebug config file

Before jumping into VSCode, first we have to clear up a few things about Xdebug to fully grasp the changes we’re going to make to the IDE. The information was first introduced on the topic of the [command directive](https://dev.to/jackmiras/docker-compose-for-a-laravel-app-ie7#app-command-directive) in a previous post. You will notice that at some point a **xdebug.ini** file gets copied from a local **.docker** folder into **/etc/php8/conf.d/50_xdebug.ini** at the container.

Even though the content of the file got shown, I intentionally didn't explain its content so that we could explore the debugging topic all at once, going all the way from configuring Xdebug to using it with an IDE.

Down below, we have the same Xdebug config file from the previous post, placed at **.docker/xdebug.ini** on the root of our Laravel project. Each line of code will be explained further, but in case you want to know every configuration that you can add in this file, check the [Xdebug documentation](https://xdebug.org/docs/all_settings).

```bash
zend_extension=xdebug.so
xdebug.mode=develop,coverage,debug,profile
xdebug.idekey=docker
xdebug.start_with_request=yes
xdebug.log=/dev/stdout
xdebug.log_level=0
xdebug.client_port=9003
xdebug.client_host=<YOUR_COMPUTER_IP>
```

##### Explaining xdebug.ini

- zend_extension=xdebug.so

A Zend extension hooks into “lower-level” languages; a single extension can be both a PHP and a Zend extension; despite being very uncommon, it's possible, and Xdebug is a good example of it.

- xdebug.mode=develop,coverage,debug,profile

This setting controls which Xdebug features get enabled; according to the documentation, the following values will get accepted:

    - develop
        Enables Development Helpers, including the overloaded var_dump().
    - coverage
        Enables Code Coverage Analysis to generate code coverage reports, mainly with PHPUnit.
    - debug
        Enables step-debugging. This can be used to step through your code while it is running and analyze the values of variables.
    - profile
        Enables Profiling, with which you can analyze performance bottlenecks with tools like CacheGrind.

- xdebug.idekey=docker

Controls which IDE key Xdebug should pass on to the debugging client or proxy. The IDE Key is only important for use with the DBGp Proxy Tool, although some IDEs are incorrectly picky as to what its value is. The default is based on the DBGP_IDEKEY environment setting. If it is not present, the default falls back to an empty string.

- xdebug.start_with_request=yes

The functionality starts when the PHP request starts and before any PHP code is executed. For example, xdebug.mode=trace and xdebug.start_with_request=yes, start a function trace for the whole request.

- xdebug.log=/dev/stdout

Configure Xdebug's log file, but in this case, we are redirecting the log content to the default stdout of our container.

> In case you don't want to see these logs you can comment out this line of your **.docker/xdebug.ini** file by changing the line to `;xdebug.log=/dev/stdout`.

- xdebug.log_level=0

Configures which logging messages should be added to the log file. In this case, we are instructing Xdebug to log only errors in the configuration. If you want to see more information, you can use level 7 for log info or level 10 for log debug.

- xdebug.client_port=9003

The port to which Xdebug tries to connect on the remote host. Port 9003 is the default for both Xdebug and the Command Line Debug Client. As many clients use this port number, it is best to leave this setting unchanged.

- xdebug.client_host=\<YOUR_COMPUTER_IP\>

Configures the IP address or hostname to which Xdebug will attempt to connect when initiating a debugging connection. This address should be the address of the machine where your IDE or debugging client is listening for incoming debugging connections.

Down below, you can see how to get your IP address correctly in the main OS the developers use. In case you are using a different OS, the commands may serve as a base to try to extrapolate a solution for your use case.

macOS:

```bash
ipconfig getifaddr en0
```

Windows with WSL:

```bash
grep nameserver /etc/resolv.conf | cut -d ' ' -f2
```

Linux (Debian-based distros):

```bash
hostname -I | cut -d ' ' -f1
```

Once you have correctly found your IP address, you can place it into xdebug.client_host as mentioned before, and that will leave you with a directive looking similar to this: xdebug.client_host=192.168.0.158.

In summary, you've instructed Xdebug to start with a request and try to send the debug events to the host with the IP 192.168.0.158 on port 9003. Since the IP represents your computer, when configuring Visual Studio Code to connect to Xdebug, the configuration will be extremely similar to when connecting to localhost.

### Visual Studio Code

As you may already know, Visual Studio Code, or, VSCode for short, is a source-code editor made by Microsoft for Windows, Linux, and macOS. Features include support for debugging, syntax highlighting, intelligent code completion, snippets, code refactoring, and embedded Git.

With that being said, you may be wondering: “What do we need to have VSCode with all the aspects of an IDE with full-featured debugging?”

For starters, we will need to install the [PHP Debug](https://marketplace.visualstudio.com/items?itemName=xdebug.php-debug) official plugin:

![install-php-debug-plugin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gnceu06l7cr9ex7w7drd.png)

Thereafter, a file called launch.json has to be generated; this file gets used by the debugger in any language, which means that a part of this process can be reused when configuring the debugger on VSCode for other languages.

You can generate the file by clicking in _Run and Debug > Create a launch.json file > PHP_, as the following screenshot shows:

![setting-up-debug](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tuvd2ox04kf0iyeqhxx4.png)

Finally, the **launch.json** file will be created with a specification of `version` and a `configurations` array with three configurations: `Listen for Xdebug`, `Launch currently open script` and `Launch Built-in web server`, as the screenshot shows:

![debug-launch-json-with-configurations](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g50c5oyelytff2n5l6dv.png)

Now, to properly configure the **launch.json** file, you have to replace the `configurations` array by the following:

```json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for XDebug on Docker",
            "type": "php",
            "request": "launch",
            "port": 9003,
            "pathMappings": {
                "/var/www/html/": "${workspaceFolder}"
            }
        }
    ]
}

```

#### Explaining launch.json

- name

Indicates the name given to a configuration object.

- type

Indicates the underlying debugger is being used.

- request

Indicates whether the configuration is intended to _launch_ the program or _attach_ to an already running instance.

- port

Indicates the port on which to listen for Xdebug

- pathMappings

Indicates a mapping of server paths to local paths.

> When using /var/www/html/ as key, VSCode knows that the files at the container are under that path, and by using the ${workspaceFolder} as value, VSCode knows that locally the project files are under the current opened directory.

Lastly, as a final step, click on the `Start Debugging` button and set the breaking points wherever you need.


![debug-docker](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2e8829wenyykaanocrza.png)

Now, Xdebug is finally configured in your VSCode, and you can enjoy a more robust debugging tool with the potential to speed up your entire workflow.

___

I encourage you to leave this launch.json file with its content in the project, so other team members can just clone the project, run the containers, and enjoy a full-featured debug solution running in a container environment.


Happy coding!