XDebug, un vieil ami

Publié le

7 avr. 2025

Introduction

C’est l’histoire d’un vieil ami… Souvent décrié, mais je vous jure, il est vraiment sympathique. Depuis plus de 15 ans, je code en pair avec celui-ci, et non, ce n’est pas une IA. Je n’ai pas fait un projet sans lui, il m’a aidé à comprendre, corriger, optimiser mon code depuis tant de temps. Il m’a même sorti de situation incompréhensible sur laquelle j’étais bloqué depuis plusieurs jours. Il n’était pas sans défaut, souvent considéré comme trop lent pour mes collègues, voire impossible de travailler avec lui. On peut dire que c’était un grand incompris. Heureusement, depuis sa version 3, il semble beaucoup fréquentable.Vous l’avez compris, je vous parle d’XDebug (le titre de l’article aide pas mal). 

XDebug permet de faire tout un ensemble de choses, profiling, debugging, code coverage. Il reste pour moi un outil indispensable au développeur PHP.

Combien de fois avez-vous mis des dump(‘toto’) ou des var_dump(‘titi’) dans votre code puis rafraichie votre navigateur ou relancer votre commande ? Voir pire, parsemer votre code de var_dump(‘1’), var_dump(‘2’) entre chaque condition ou ligne de votre code ? Ou simplement dumpé une variable pour au final se rendre compte que l’erreur était ailleurs ? Si vous vous êtes reconnu dans les phrases précédentes, je vous en prie arrêtez votre massacre, utilisez XDebug

Lorsque l’on parle XDebug, vous allez entendre les plus vieux râler… Souvent comme ceci : 

  • C’est galère à mettre en place Spoiler : Franchement pas tant que ça, mais continuez votre lecture 😏
  • Avec docker, c’est impossible Spoiler : Mais si, on va vous expliquer ci-dessous
  • Ça alourdit l'exécution du code, c’est impossible de travailler avec Spoiler : plus depuis la version 3 🐌

Fonctionnement d’XDebug

Le fonctionnement d’XDebug repose sur 2 parties. L’une est un module PHP et l’autre sur un serveur généralement lancé par votre IDE. La communication entre les deux s’effectue avec le protocole DBGP. Si vous aimez la documentation, elle est ici https://xdebug.org/docs/dbgp 

Il permet principalement de piloter l'exécution du code distant (via le module PHP) et permet la récupération des informations (variable, état de l'exécution du code). Le protocole de communication passe par les grandes étapes suivantes : 

  1. Initialisation de la connexion
  2. L’IDE envoie l’ensemble des conditions d'arrêts demandé par l’utilisateur.
  3. Puis lorsque le code atteint un breakpoint (point d'arrêt) on rentre dans la boucle d’échange suivante :
    1. Échange permettant de contrôler l'exécution du code
    2. Récupération des informations

Avant la version 3, avoir simplement l’extension chargée dans PHP engendrait une sévère dégradation des performances. Ce qui rendait le travail difficile avec d'autres développeurs qui n'utilisaient pas cet outil. 

Source : https://php.watch/articles/xdebug2-vs-3-benchmark

Alors rien de bloquant en soit, une simple modification de la configuration PHP et voilà, on active ou désactive notre cher XDebug, mais lorsqu’on travaille dans un environnement “packagé” vagrant pour les plus vieux ou maintenant docker, on arrivait vite à se dire que c’était trop chiant de bosser avec. Avec la version 3, l’impact d’avoir XDebug chargé dans PHP en mode “off” est vraiment minime. 

XDebug, les différents modes

C’est bien beau tout cela, mais à quoi ça sert concrètement XDebug ?C’est un “multi tools” qui permet de faire plein de choses différentes, ici un petit tour d’horizon des possibilités :

Trace & Profile

Ces deux modes permettent une analyse de la performance de l'exécution de votre code. Il y a cependant quelques subtilités entre les deux.


trace

profiling

Fonctionnalités communes

Permettre le suivi de l'exécution du code (stacktrace) et du temps de celui-ci. 

Affichage du résultat

Le résultat est directement lisible par un humain, il s’agit au choix, soit du simple texte soit de l’html

La sortie de celui-ci est un fichier cachegrind. C’est un format qui est généralement lisible par votre IDE ou un logiciel externe.

Profiling de la mémoire

Oui

Non

Suivi des variables

Oui

Non

C’est un mode pratique en cas d’analyse des performances même si j’ai tendance à préférer la solution de blackfire pour sa lisibilité et leurs suggestions. Plus d’information sur blackfire ici : https://docs.blackfire.io/introduction

Develop

Ce mode permet d’améliorer l’affichage d'informations relatives au débug de votre application. Par exemple, plutôt que de simplement vous donner un message de warning, il vous permettra de dire d'où il provient. 

Aussi, il permet d’afficher de manière plus lisible vos données sortie avec un var_dump

Je n’ai personnellement jamais utilisé ce mode, on lui préférera son copain debug Plus d’information ici : https://xdebug.org/docs/develop

Debug

C’est bien ce mode qui est votre meilleur ami. Aussi bien lors de la phase de debug que lors de la phase de développement de votre projet. C’est grâce à celui-ci que l’on va pouvoir piloter l'exécution du code via notre IDE 

Mais je vais pouvoir vous détailler cela dans la partie suivante Utilisation

Coverage

Il existe plusieurs solutions permettant de faire du code coverage lors de l'exécution de vos tests. Xdebug est l'une d'entre elles, mais l’on peut aussi citer Pcov ou phpdbg. Je ne vais pas m’étendre sur le sujet, car je n’ai pas replongé dedans depuis la sortie de XDebug 3. Ce que je peux vous dire, c’est que XDebug est souvent considérée comme plus lente que les autres solutions. Mais que personnellement, j’ai trouvé sa mise en œuvre bien plus simple. Lien suggéré pour plus d’informations :https://thephp.cc/articles/pcov-or-xdebug?ref=phpunit

GCstats

Je n’ai personnellement jamais utilisé ce mode, il permet de suivre l’activité du garbage collector de PHP. Je ne peux pas vous en dire grand chose d’autre que ce que donne la documentation officielle.

Le Garbage Collection (GC) en PHP peut avoir un impact sérieux sur la mémoire et les performances, donc comprendre quand il est déclenché et quelle est l'efficacité de chaque exécution vous permet d'optimiser vos programmes. Le moteur PHP ne fournit pas de mécanisme pour collecter des statistiques sur le garbage collection, mais Xdebug le fait désormais.

Plus d’information ici : https://xdebug.org/docs/garbage_collection

Mise en place avec docker

Pour mettre en place XDebug rien de plus simple. Il faut respecter les étapes suivantes :

  1. Avoir PHP avec le module XDebug (suivant votre version, une compilation pourra être nécessaire.)
  2. Avoir une interface permettant de piloter XDebug c’est possible de Vim à VSCode, PhpStorm, etc…
  3. Configurer correctement votre serveur XDebug ainsi que votre mode. 

Je pense que la majorité d’entre vous, utilise docker. Si ce n’est pas le cas, pas d’inquiétude, oubliez simplement les parties spécifiques à docker et vous avez les grandes lignes nécessaires. Aussi, la documentation officielle couvre assez largement l’installation hors docker : https://xdebug.org/docs/install

Mise en place du module

Si vous n’avez pas XDebug dans votre noyau PHP, il sera possible de l’installer avec pecl. Il suffira ensuite de le charger dans la configuration PHP. 

RUN pecl install xdebug-3.4.0 && \
   docker-php-ext-enable xdebug && \
   rm -rf /tmp/pear

⚠️ Disclaimer : Même si XDebug en mode off n’engendre que peu d’overhead, je vous déconseille fortement de l’utiliser dans vos builds de productions. On privilégiera l’utilisation des stage dev / prod pour une meilleure séparation. 

Configuration du fichier xdebug.ini

xdebug.client_port=9003
xdebug.idekey=IDEKEY
xdebug.mode=debug
xdebug.client_host=host.docker.internal
xdebug.start_with_request=yes

Alors détaillons ici les paramètres que nous donnons à xdebug. 

xdebug.client_port

Permet de définir le port du serveur XDebug que le module devra contacter, en l'occurrence celui configuré dans votre IDE.

https://xdebug.org/docs/step_debug#client_port

xdebug.idekey

Essentiel lorsque l’on utilise un proxy, il semble que ce soit mieux de la mettre suivant l’IDE qu’on utilise. Je vous renvoie vers la documentation pour plus d’information
https://xdebug.org/docs/step_debug#idekey

xdebug.mode

Cela correspond au mode que l’on souhaite avoir et que j’ai décrit ci-dessus. Sachez qu’il est possible de panacher avec une virgule exemple : trace,develop
https://xdebug.org/docs/step_debug#mode

xdebug.client_host

L’IP de votre serveur XDebug (généralement celui où est votre IDE). Ici dans un environnement docker, nous utilisons l’astuce du host.docker.internal, j’y reviens dans la configuration du docker-compose

https://xdebug.org/docs/step_debug#client_host

xdebug.start_with_request

Personnellement je le setup toujours en yes, mais il est possible d’utiliser le mode trigger qui permet un control plus fin de son exécution.

https://xdebug.org/docs/step_debug#start_with_request

Il ne nous reste que la partie docker, voici un docker-compose.yml


version: '3.8'
services:
  php:    build:
      context: .
      dockerfile: docker/php/Dockerfile
    volumes:      - './docker/php/config/xdebug.ini:/usr/local/etc/php/conf.d/debug.ini'
    extra_hosts:
      - host.docker.internal:host-gateway

Pour ceux qui font du docker, rien de bien méchant. 

La première commande (build) permet de définir ou se situe notre fichier de build docker (le Dockerfile) et depuis quel context (en l'occurrence la racine de l'exécution de la commande docker)

Ici avec la configuration des volumes, nous allons monter le fichier xdebug.ini dans le répertoire de chargement de configuration php. Attention, celui-ci peut changer suivant votre build php. 

Et avec l’extra_hosts nous ajoutons dans docker un DNS pointant vers l'hôte du container en l'occurrence notre PC de développement. 

Nous avons maintenant notre module prêt à envoyer les informations à l'hôte permettant de piloter l'exécution de notre code à l’adresse host.docker.internal sur le port 9003 avec l’idekey IDEKEY dès que PHP démarre.

Configuration du serveur

Maintenant côté “IDE”.

Étant évangéliste non-officiel de PHPStorm, je vais dans la suite de l’article utiliser des captures d’écrans sur la configuration de XDebug dans celui-ci, cependant, vous pouvez facilement faire un parallèle avec votre IDE préféré. Je ne suis pas vache, on parle un peu aussi de VSCode. 

Menu > Run > Edits configurations

 

Ici, nous allons donner un nom (peu importe quoi), filtrer par IDEKey (optionnelle normalement) puis cliquer sur les à côté de Server. 

Vous allez avoir la fenêtre suivante :

Idem, vous pouvez mettre le nom que vous voulez. En Host, vous pouvez écouter sur l’ensemble des interfaces réseaux 0.0.0.0, le port précédemment configuré 9003 et le type de débugger Xdebug

Vous remarquez ici que nous avons la nécessité de faire une relation entre les fichiers sur le serveur distant (notre container) et ceux en local. Il est donc nécessaire de cocher l’option et de renseigner le path correspondant. Ici dans mes fichiers projets, la racine de mon projet php est dans apps/back, hors sur mon container le code s'exécute dans /usr/src/app, je dois donc faire le lien entre les deux.

Pour VSCode, les mêmes principes s’appliquent. Vous créez une configuration de débug, puis vous renseigner les informations nécessaires. Ici un exemple de fichier de configuration : 

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for Xdebug",
            "type": "php",
            "request": "launch",
            "port": 9003,
            "pathMappings": {
                "/usr/src/app": "${workspaceFolder}"
            }
        }
    ]
}

Bien maintenant nous sommes prêts à débugger notre application. 

💡Si vous voulez, vous pouvez lancer votre serveur et vérifier qu’il écoute sur le bon port, la commande netstat est votre ami. 

pbrun@pop-os: netstat -natp | grep 9003
tcp6       0      0 :::9003           :::*             LISTEN      106909/phpstorm     

Utilisation 

Je vous invite si ce n’est pas déjà fait, à relancer rebuild votre docker et relancer vos containers. 

Ensuite, vous pouvez aller sur l'interface de votre site en développement et observer la différence de performance. 

Voici les étapes pour lancer un débug : 

  1. Lancer votre serveur précédemment configuré. Vous pouvez remarquer ici que dans Thread & Variables, le serveur est en attente de connexion. 
  1. Mettre un point d'arrêt (la pastille rouge appelé aussi breakpoint) sur la partie de code que vous souhaitez déboguer. ⚠️ Il n’est pas possible de mettre des points d’arrêt partout, par exemple, il n’est pas possible d’en mettre au beau milieu d’un tableau.
  1. Il ne vous reste plus qu'à rafraîchir la page de votre projet, et normalement l'exécution de votre code s'arrêtera et vous pouvez piloter celui-ci. Une fois celui arrêté, vous pouvez consulter l’ensemble des variables disponibles, mais aussi la callstack. Plus d'informations disponibles ci-dessous.

Comment l’utiliser ? 

Voici votre interface graphique permettant de naviguer dans l'exécution du code. Ça semble logique, mais il ne sera malheureusement pas possible de revenir en arrière dans celui-ci. Si vous dépassez l’endroit qui vous intéresse, vous pouvez relancer :) 

Rerun

Relance votre serveur XDebug. Intéressant si vous avez un breakpoint (point rouge) qui n’est pas pris en compte. 

Stop

Arrêt du débogage. Attention, celui-ci va couper le thread PHP, vous allez donc avoir une page blanche dans votre navigateur.

Resume

Permet de relancer l'exécution du code et d’aller jusqu’au prochain breakpoint ou la fin du programme.

Step over

Permet d’aller jusqu'à la prochaine ligne. Par exemple dans le cas d’un if, vous allez pouvoir suivre l'exécution du code. 

Step into

Permet de “descendre” d’un niveau, ou autrement dit de rentrer dans l'exécution d’une fonction. Dans de nombreux cas, vous allez potentiellement passer par un ensemble de fonctions y compris celle que vous ne souhaitez pas debugger. Il suffit de continuer les step over, de sortir ou de poser un breakpoint plus bas. 

Step out

Permet de “remonter” la pile.

View breakpoints

Permet de lister l’ensemble des breakpoints dans votre projet.

Ici, il est possible d'exécuter du code, d’afficher des variables en permanence qui seront rafraîchies suivant l’endroit de l'exécution. 

⚠️ Attention si vous modifiez la valeur d’une variable, celle-ci sera modifiée lors de chaque échange avec XDebug.

Sur la partie gauche, vous avez la callstack. Au clique, il est possible de naviguer dans la pile d’appels et d'observer les variables à chaque étape. Bien évidemment, il n’est pas possible de “bouger” dans celle-ci, ce n’est pas possible uniquement dans le fichier courant. 

Et enfin la liste des variables locales, il est possible d’en explorer la profondeur.

Tips

  • Imaginons que vous ayez un souci sur une requête Doctrine. Il est possible d'exécuter directement une commande dans le bon contexte. Vous allez pouvoir mettre un point d'arrêt dans votre méthode de repository. Une fois l'exécution stoppée, vous pouvez modifier la requête et l'exécuter localement autant de fois que nécessaire ou simplement afficher son SQL
  • Avec un clique droit sur le point d'arrêt (pastille rouge), il est possible de définir des conditions d'arrêt !
  • Sans redémarrer votre XDebug, il est possible d’activer celui-ci en ligne de commande php -dxdebug.mode=debug bin/console app:my:commande
  • Avec un clique droit sur la pastille rouge, il est possible de simplement désactiver temporairement un breakpoint. 
  • N’oubliez pas qu’il est possible d’utiliser le mode trigger pour activer ou non le debugging de votre application
  • XDebug est l'outil idéal pour comprendre le fonctionnement d’un Framework ou d’une partie de celui-ci !

Voilà, maintenant, je vous en conjure, configurez XDebug sur votre projet. Débugger, explorer votre code ou celui de vos vendors, déboguer efficacement sans rafraîchir en permanence votre navigateur. 

Publié par

Pierre Brun
Pierre Brun

Pierre, aka Alu, takes care of our Symfony projects, guides our customers and shares his best practices with his colleagues. As a fan of Japan, he regularly leaves us to live adventures there.

Commentaires