Published on

VSCode + Docker + PHPUNIT + XDEBUG SETUP

Authors

Update December 2023: While this article is still valid in some cases, I definitely don't use Xdebug that much now. Having refactored our tests, with better bounded context and separation, the last time I Xdebugged was beginning of the year

Recently I have been involved in more than one project with the same characteristics:

  • Laravel 5.x, 6.x (no Sail here)
  • Docker environment
  • PHPUnit test suite

often with quite a good test coverage (yeah!), lots of moving parts (less yeah), and the policy of leaving you alone to figure out the test suite by yourself (challenging but still yeah!).

This means that sometimes tests fail when they shouldn't. And this means that sometimes you have to figure out why they fail.

I have no shame to admit that 50% of the time ONE dump() is enough and another 30% is covered by using LOTS of dump()s. This leaves us with 20% of occasions in which you need to xdebug the code while running the test.

Environment setup

Docker services

Let's assume you have a docker-compose.yml configuration file with an app service representing your application:

version: '3.8'
  services:
      
      # Application
      app:
          build:
              context: .
              dockerfile: app.dockerfile
          working_dir: /var/www
          volumes:
              - ./:/var/www
          depends_on:
              - "database"
  ...

The app.dockerfile file quite simply creates a phpX.X service with minimum setup to run a Laravel application

FROM php:7.2-fpm

RUN apt-get update && apt-get install -y  \
    libfreetype6-dev \
    libjpeg-dev \
    libpng-dev \
    libwebp-dev \
    libxml2-dev \
    libxslt-dev \
    vim \
    git \
    unzip \
    --no-install-recommends
    
RUN docker-php-ext-install \ 
    pdo_mysql calendar \
    pcntl \
    gd \
    gettext \
    exif \
    mysqli \
    shmop \
    sockets \
    sysvmsg \
    sysvsem \
    sysvshm \
    wddx \
    xsl \
    zip

Create a new Docker machine for debugging

The idea now is to have a Docker service completely identical to the one running our development environment but with xdebug enabled. To do so add this to your docker-compose.xml file.

app_dbg:
  build:
    context: .
    dockerfile: app.dockerfile
  args:
        - INSTALL_XDEBUG="true"
  working_dir: /var/www
  volumes:
    - ./:/var/www
  depends_on:
    - "database"

The definition of the service has to be identical to the app service but for the lines

args:
  - INSTALL_XDEBUG="true"

you can also use the app.dockerfile as a starting point using FROM.

Now if you launch $ docker-compose up -d both app and app_dbg services should be up and running.

What is this service missing? XDebug of course!

XDebug configuration

To configure XDebug we need to have a xdebug.ini configuration file that will let us connect to xdebug later on. I usually save all the docker related configuration files in a docker/ subdirectory. In this case the file should be docker/php/xdebug.ini with content

xdebug.default_enable=1
xdebug.remote_enable=1
xdebug.remote_port=9000
xdebug.remote_handler=dbgp
xdebug.remote_connect_back=0
xdebug.remote_host=host.docker.internal
xdebug.idekey=VSCODE
xdebug.remote_autostart=1

We could also just plainly add the lines to app.dockerfile but we don't want to have it installed by default as it would slow down our tests during normal runs. We can instead install it behind a configuration variable that we can switch off and on from our environment:

ARG USE_XDEBUG=false
RUN if [ ${USE_XDEBUG} = true ]; then \
    # Install the xdebug extension
    pecl install xdebug && \
    docker-php-ext-enable xdebug && \
    chmod 777 /usr/local/etc/php 
  ;fi
  • Define a parameter USE_XDEBUG with default false value
  • If USE_XDEBUG has been set to true then
    • install xdebug
    • enable it and make everything inside /usr/local/etc/php writable (for logfiles mostly)

Note: the last line might not be necessary. Sometimes I had bugs related to permissions so I went 777 on everything in that directory.

Note 2: If you have problems with segmentation faults or memory issues try to install xdebug2.x by changing line 4 with

pecl install xdebug-2.9.8 && \

Now we need to configure xdebug in VScode in order for it to listen to our docker machine app_dbg.

The configuration should be saved in .vscode/launch.json

{
    "version": "0.2.0",
    "configurations": [{
            "name": "Listen for XDebug",
            "type": "php",
            "request": "launch",
            "hostname": "app_dbg.local",
            "port": 9000,
            "log": true,
            "externalConsole": false,
            "pathMappings": {
                "/var/www/html": "${workspaceRoot}",
            },
            "ignore": [
                "**/vendor/**/*.php"
            ]
        },
    ]
}

Install and configure ''Yet Another PHPUnit'' plugin for VSCode

Head to the marketplace link for the plugin or search for the plugin on the marketplace.

After installing and activating it, open your settings.json file and add these lines

"yet-phpunit.docker.enable": true,
"yet-phpunit.docker.command": "docker exec -w /var/www/html <projectname>_app_1",
"yet-phpunit.docker.paths": {
  "<your working directory>": "/var/www/html"
},
  1. <your working directory> = where you store your project files. ie /Users/user/Projects/code
  2. <projectname = if you don't override docker compose, this is usually the name of the directory of your codebase, so in the example before <projectname>_app_1 is code_app_1

You could also set a name for the container and use that instead of having Docker name it by default.

Run Test without xdebug

To run a test in xdebug just make sure that you're docker containers are and up running using docker-compose up -d and then click on the 'Run test' button provided by the xcode wonderful plugin.

Run test with xdebug

  1. Make sure that the extension PHP Debug is installed
  2. Click on the icon
  3. Select "Listen for XDebug" as a configuration from the dropdown menu on the top and then click the green arrow to start the xdebug listener.
  4. Modify settings.json and edit the to use the app_dbg container
"yet-phpunit.docker.command": "docker exec -w /var/www/html <projectname>_app_dbg_1",

  1. run the test same as above

The same rule for projectname applies here.

Conclusions

The setup is quite quick and it works well. It is a bit cumbersome to build it and if you come from PHPStorm you would probably already have all the setup working, but still... it works.

The only downfall I found so far is the settings.json switch I have to make every time I need to switch between "with xdebug" and "without xdebug" but between CMD+P and cursor history it usually takes me 10 seconds to do the change.

But of course if someone has a better solution (ie. extend the 'Yet another PHPUnit" plugin with a "Run test with xdebug" button). Please let me know!