Débugger une application PHP dockerisée avec XDebug et Vscode

Introduction

Pendant des années j’ai développé en PHP en débuggant à coup de ‘var_dump()’ et autres ‘exit()’. Mettre en place l’extension XDebug était suffisemment complexe pour que je me dise: “la prochaine fois je prendrais le temps”… Il a fallu du temps avant que cette prochaine fois soit la bonne! Avec Docker et VScode il est désormais facile de configurer XDebug. Aujourd’hui je ne me vois pas revenir en arrière.

Ce tutoriel prend exemple d’une application extrêmement simple. Si vous êtes pressé, tous les fichiers utilisés dans ce tuto sont disponible depuis ce repository github.

Une application PHP fonctionnant sous docker

Un prérequis à ce tutoriel est d’avoir une application PHP fonctionnant sous docker. Si c’est déjà votre cas, vous pouvez directement passer à la section suivante. Pour des raisons de simplification tous les fichiers de ce tuto seront placés dans un même dossier. Libre à vous d’améliorer la structure du projet comme bon vous semble.

Nous allons avoir besoin de :

  • Un fichier docker-compose.yml pour orchestrer 2 services constituant notre serveur web : php-fpm et nginx:
version: '3.7'

services:

  php_app:
    build:
      context: .
      dockerfile: Dockerfile-php-app
    volumes:
      - ./:/var/www/html

  nginx_app:
    image: nginx:1.17.0-alpine
    ports:
      - 8080:80
    volumes:
      - ./:/var/www/html
      - ./app.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - php_app

  • Le fichier Dockerfile correspondant

J’ai choisi de le nommer Dockerfile-php-app pour expliciter le fait qu’il s’agisse de la description de l’image PHP et non pas de l’image nginx.

FROM php:7.1-fpm-alpine

# php configuration
COPY php.ini /usr/local/etc/php/

  • Le fichier php.ini

Pour le moment le contenu de ce fichier est sans importance. Il sera utile un peu plus loin pour y ajouter la configuration XDebug.

[PHP]

;;;;;;;;;;;;;;;;;;;
; About php.ini   ;
;;;;;;;;;;;;;;;;;;;
; PHP's initialization file, generally called php.ini, is responsible for
; configuring many of the aspects of PHP's behavior.

error_reporting = E_ALL;
log_errors = On;
display_errors = On;
error_log = /dev/stderr;

  • Ajoutons à cela la configuration du serveur nginx qui va convertir notre PHP en HTML.

Le nom du fichier est app.conf comme indiqué dans le fichier docker-compose.yml.

server {
    root /var/www/html;

    access_log /dev/stdout;
    error_log /dev/stderr;

    charset utf-8;

    location / {
        # try to serve file directly, fallback to index.php
        try_files $uri /index.php$is_args$args;
    }

    location ~ ^/index.php(/|$) {
        fastcgi_pass php_app:9000;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}
  • Pour finir, créeons un fichier index.php avec quelques instructions:
<p>J'adore les maths.</p>

<ul>
    <?php
        for ($i = 0; $i < 10; $i++) {
            echo sprintf("<li>%s * 5 = %s</li>", $i, $i*5);
        }
    ?>
</ul>

Dans votre terminal, tapez docker-compose up -d and ouvrez l’url http://localhost:8080/. Cela devrait fonctionner.

Configurer Xdebug avec VScode

Les choses intéressantes commencent ici. La première chose à faire est de mettre à jour l’image docker pour inclure l’extension XDebug. Ajoutons ces 3 lignes au fichier Dockerfile-php-app:

...

# Xdebug configuration
RUN apk add --no-cache $PHPIZE_DEPS # dépendances requises par pecl
RUN pecl install xdebug && docker-php-ext-enable xdebug

Puis ajoutons la configuration de XDebug au fichier php.ini:

;xdebug configuration
xdebug.remote_enable = 1
xdebug.remote_port = 9099
xdebug.remote_autostart = 1
xdebug.remote_connect_back = 0
xdebug.remote_host = 192.168.0.30

Modifier la valeur de xdebug.remote_host avec celle de votre IP local à votre réseau (l’IP par laquelle votre ordinateur est accessible depuis un conteneur docker).

Sur mon propre ordinateur fonctionnant sous Ubuntu Bionic, la commande hostname -I | awk '{print $1}' m’affiche cette IP. Tapez docker-compose build pour mettre à jour l’image PHP, suivi par docker-compose up -d une nouvelle fois.

Enfin et surtout, il s’agit de créer la configuration VScode. Commencez par installer l’extension PHP Debug par Felix Becker. Puis dans VScode selectionnez l’onglet debug, cliquez sur Add Configuration.. puis selectionnez l’environnement PHP. Un fichier launch.json va s’ouvrir. Écrasez son contenu avec celui ci-dessous:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for XDebug",
            "type": "php",
            "request": "launch",
            "port": 9099,
            "pathMappings": {
                "/var/www/html/": "${workspaceRoot}"
            }
        }
    ]
}

Ajouter un breakpoint dans le fichier index.php and tapez sur le bouton Listen for XDebug. Vous pouvez parcourir http://localhost:8080/ une seconde fois. Vscode devrait intervenir et prendre le relai à l’endroit de votre breakpoint! Faites en sorte que VScode soit bien ouvert au niveau du dossier contenant tous vos fichiers et non pas un répertoire parent. Sans cela le chemin relatif à VScode et docker sera erroné et XDebug ne se déclenchera pas.

Problèmes connus

D’après la documentation Xdebug, Xdebug ne démarrera pas une session de debuggage automatiquement quand un script est lancé. Je n’ai pas rencontré ce problème personnellement, mais une bonne manière de tester cela est de lancer le débuggeur dans VSCode puis d’exécuter la commande suivante:

$ docker-compose exec php_app php index.php

Si rien ne se passe, modifier le service php_app du fichier docker-compose.yml en ajoutant une variable d’environnement:

services:

  php:
    ...
    environment:
      XDEBUG_CONFIG: idekey=session_name

Exécuter ces commandes à nouveau pour vérifier que tout fonctionne correctement:

$ docker-compose up -d
$ docker-compose exec php_app php index.php

Et si j’ai plusieurs conteneurs PHP ?

Imaginons que votre application soit composée de plusieurs backends PHP. Dans ce cas il est probable que vous ayez plusieurs services pour les faire fonctionnant dans des conteneurs séparés, et le port XDebug (ici 9099) sera alors exposé plusieurs fois sur votre machine par des applications différentes ce qui n’est pas possible.

Une solution possible à ce problème est de faire usage de l’instruction ARG dans le Dockerfile.

Voici ce que vous obtenez avec 2 services PHP:

  • Le fichier docker-compose.yml
version: '3.7'

services:

  php_app_1:
    build:
      context: .
      dockerfile: Dockerfile-php-app
      args:
        XDEBUG_PORT: 9042
    volumes:
      - ./:/var/www/html

  php_app_2:
    build:
      context: .
      dockerfile: Dockerfile-php-app
      args:
        XDEBUG_PORT: 9043
    volumes:
      - ./:/var/www/html

Avec les instructions suivantes dans le Dockerfile-php-app pour les images PHP, exécuter docker-compose build va créer 2 images exposants des ports différents.

...

ARG XDEBUG_PORT
RUN sed -i "s/xdebug.remote_port = 9099/xdebug.remote_port = $XDEBUG_PORT/g" /usr/local/etc/php/php.ini

Pensez à modifier la valeur du port des fichiers launch.json pour chaque application.

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for XDebug",
            "type": "php",
            "request": "launch",
            "port": 9043,
            "pathMappings": {
                "/var/www/html/": "${workspaceRoot}"
            }
        }
    ]
}

Si cet article vous a rendu service, n’hésitez pas à starrer ce repo en guise de remerciement ! ⭐