Well … say you want to run your unit-tests against multiple PHP versions and still enjoy the comfort of your IDE + Xdebug :P

How ???

Easy once you know how, but a little fiddly the first time around :)

Step 1: Create a Docker Image with your Dependencies

As an example I’ll use a typical WordPress unit-test setup here. In my case I wanted to be able to run my Unittests with PHP7, but not have to pollute my Debian Jessie/Stretch install, with a non apt-packaged PHP7 alongside the PHP 5.6.16 that currently comes with it. Still I wanted this to run nicely from IDEA :)

All the code for the following can be found in here on GitHub.

So since we’re gonna be playing with WordPress we’ll need a few extensions on top of our standard PHP7 CLI docker image from the official repo. Also we need PHPUnit and XDebug. Lastly we are going to use the composer installation of PHPUnit, so we need composer also.

The resulting Dockerfile comes out to something like this:

FROM php:7-cli

RUN apt-get update && apt-get install -y mysql-client libmysqlclient-dev curl git \
    libfreetype6-dev libjpeg62-turbo-dev libmcrypt-dev libpng12-dev \
    && docker-php-ext-install iconv mcrypt \
    && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
    && docker-php-ext-install gd \
    && docker-php-ext-install zip \
    && docker-php-ext-install mysqli \
    && docker-php-ext-install opcache \
    && docker-php-ext-install mbstring 

RUN curl -sS https://getcomposer.org/installer | php
RUN mv composer.phar /usr/local/bin/composer

RUN composer global require "phpunit/phpunit"
ENV PATH /root/.composer/vendor/bin:$PATH
RUN ln -s /root/.composer/vendor/bin/phpunit /usr/bin/phpunit

RUN pecl install xdebug-2.4.0RC3 
RUN echo "zend_extension=xdebug.so\nxdebug.cli_color=1\nxdebug.remote_autostart=1\nxdebug.remote_connect_back=1" > /usr/local/etc/php/conf.d/xdebug.ini

So far so good :) We can now build this thing ( which I already did for you here: DockerHub under Tag: 7-cli-phpunit-xdebug ).

The import part in this is that you correctly link phpunit to be in the path. This is done in this part:

RUN ln -s /root/.composer/vendor/bin/phpunit /usr/bin/phpunit

… just trivially sym-linking the docker executable to something that is in the path for sure.

Also, and this is where it gets a little hacky for the time being unfortunately, we need to setup the networking for XDebug and the container itself. The XDebug part is where we need to force XDebug to send requests, via us echoing:


into the xdebug.ini.

Step 2: Create our new PHP-executable

What we now need is for our dockerized PHP executable to behave as similarly as possible to a plain standard PHP executable on the host system.

This can be done via the following bash script, that you can fine here.

docker run -i --rm -v "${PWD}":"${PWD}" -v /tmp/:/tmp/ -w ${PWD} --net=host --sig-proxy=true --pid=host originalbrownbear/php:7-cli-phpunit php "$@"

What this does is:

  1. Mount the current working directory into the container.
  2. Make the container use the host’s pid space
  3. Make the container use the host’s network adapter
  4. Mount the hosts tmp folder into teh container, as PHPStorm copies a file there in some cases

If you’re using your own version and not one provided by me in GitHub, you need to make sure to

chmod +x 

it so that PHPStorm can run it like any other executable.

Step 3: Setup PHPStorm/Idea correctly

This is relatively easy fortunately. Just point the PHP Interpreter setting at your bash script that you crated above, like this for example:

… and ignore the warning. This is nothing to worry about, the ini file is simply in the container and not accessible by PHPStorm.

Also set your PHPUnit settings like in the above and you’re pretty much there and should be able to run phpunit.xml files like with any other PHP + PHPUnit setup, while being able to easily jump around PHP versions.

The only difference in behavior I am currently seeing is, that I still need to manually make PHPStorm listen for incoming XDebug connections via the telephone icon set like this:

Pro Tip: Skip XDebug unless you actually need it!

In case you’re running most of your test runs without XDebug ( as you probably should … different topic though ), you shouldn’t necessarily use a container that has XDebug even installed for performance reasons. Even if the .so extension is loaded, but disabled otherwise the performance hit can be 50%+ easily for most applications!

In this case simply delete the XDebug part of the image build and create another bash script to run the second image without XDebug. All this can be found worked out in examples in the GitHub repo.

Update: How to Make This Work on Mac at Last :)

On mac our nice and easy bash script from above unfortunately turned out to not work, as discussed in the comments. Fortunately this could be resolved easily, like so:

#!/usr/bin/env bash
# beginning of "docker-machine env default" output
export DOCKER_HOST="tcp://"
export DOCKER_CERT_PATH="/Users/brownbear/.docker/machine/machines/default"
export DOCKER_MACHINE_NAME="default"
# End of "docker-machine env default" output
/usr/local/bin/docker run -i --rm -v "${PWD}":"${PWD}" -w ${PWD} --net=host --sig-proxy=true --pid=host originalbrownbear/php:7-cli-phpunit php "$@"

simply paste the output of

$ docker machine env default

before the original version of the bash call and you’re good :) Only other thing you need to keep in mind is that:

Every directory you mount into a Docker container on a Mac needs to be in your users home folder!

… anything else just won’t work for permissions reasons it turns out.