Table des matières :
- Comprendre le flux Xdebug ↔ IDE (et les pièges classiques)
- Pré‑requis (versions, extension VS Code) et vérifications minimales
- Configurer Xdebug 3 côté PHP (FPM/Apache/CLI) sans se tirer une balle dans le pied
- Configurer Visual Studio Code (launch.json, écoute sur 9003, path mappings)
- Docker, VM, WSL, SSH : régler le problème réseau (client_host) proprement
- Cas PrestaShop : déboguer modules, hooks, contrôleurs Symfony et CLI
- Dépannage : breakpoints ignorés, 502 aléatoires, et hygiène de debug en équipe
Le débogage PHP dans Visual Studio Code avec Xdebug n’a rien de “magique” : c’est un aller‑retour réseau entre PHP (serveur) et VS Code (client IDE), avec des contraintes très concrètes (ports, NAT Docker, mappings de chemins, FPM vs CLI). Tant que vous traitez ça comme un problème réseau + configuration, ça marche.
Comprendre le flux Xdebug ↔ IDE (et les pièges classiques)
Xdebug 3 fonctionne en mode “client/serveur” inversé : c’est PHP qui initie la connexion vers l’IDE. Le schéma mental à garder :
- Une requête HTTP arrive (ou un script CLI démarre).
- Xdebug est actif et autorisé à se déclencher (
start_with_request+ trigger). - PHP/Xdebug ouvre une socket TCP vers l’hôte + port où VS Code écoute (par défaut 9003 en Xdebug 3).
- VS Code reçoit la session DBGp, fait correspondre les chemins (path mappings), puis gère les breakpoints.
La conséquence immédiate : tout ce qui “casse” la connectivité sortante depuis l’environnement PHP casse le débogage pas‑à‑pas. Cas typiques :
- PHP dans un conteneur Docker qui ne peut pas résoudre/joindre votre machine hôte ;
- serveur distant derrière un firewall qui bloque 9003 (ou une politique réseau d’entreprise) ;
xdebug.client_hostqui pointe vers la mauvaise IP/DNS (erreur très fréquente) ;- reverse proxy +
xdebug.discover_client_hostactivé, mais l’IP “découverte” n’est pas celle de l’IDE (ou l’en‑tête utilisé est masqué/modifié).
Et c’est aussi pour ça que des configs “copiées/collées” depuis Xdebug 2 ne fonctionnent plus : le port par défaut a changé (9000 → 9003) et plusieurs directives ont été renommées/simplifiées.
Point important côté bonnes pratiques : la documentation officielle déconseille d’activer Xdebug en production (risque de surface d’attaque + surcharge). Même en pré‑prod, gardez une activation à la demande (trigger) : vous évitez de ralentir toute l’équipe et vous limitez les “effets de bord” dans les mesures de performance. Si votre objectif est d’expliquer une lenteur (et pas de suivre une branche de code), vous gagnerez souvent du temps avec une approche orientée requêtes/logs/APM ; pour un angle PrestaShop, vous pouvez compléter avec : Optimisation de code PrestaShop — Diagnostiquer lenteurs et requêtes SQL
Pré‑requis (versions, extension VS Code) et vérifications minimales
Les exemples ci‑dessous sont validés pour PHP 8.2/8.3 avec Xdebug 3.x et Visual Studio Code (build courant 2026). Côté e‑commerce, ça colle aux stacks modernes (PrestaShop 8/9, Symfony au cœur du back-office), mais la configuration reste générique pour toute application PHP.
Avant même de toucher VS Code, assurez‑vous que Xdebug est bien installé et chargé pour le SAPI concerné : CLI, FPM, Apache mod_php n’utilisent pas toujours le même php.ini. L’article ci‑dessous détaille précisément les différences CLI/web, qui expliquent beaucoup de “ça marche en CLI mais pas dans le navigateur” (ou l’inverse) : Installer et activer Xdebug 3 pour PHP CLI et web
Côté VS Code, installez l’extension PHP Debug (debug adapter) qui implémente le protocole DBGp utilisé par Xdebug : PHP Debug (vscode-php-debug)
Vérifications minimales (CLI) :
php -v
php --ini
php --ri xdebug | sed -n '1,160p'
À confirmer :
- Xdebug apparaît bien dans
php --ri xdebug. - Les valeurs effectives sont celles que vous croyez modifier (souvent, on édite le mauvais fichier ini).
- Le
Loaded Configuration Fileet lesScan for additional .ini filessont cohérents avec votre environnement.
Vérifications minimales (web / FPM) : créez temporairement un phpinfo() ou utilisez une page “status” interne en dev pour vérifier que le FPM charge bien Xdebug. C’est particulièrement important avec PHP-FPM : on peut avoir Xdebug actif en CLI mais pas côté web.
Configurer Xdebug 3 côté PHP (FPM/Apache/CLI) sans se tirer une balle dans le pied
La configuration Xdebug “de base” pour VS Code repose sur 4 paramètres :
xdebug.mode=debug(active le step debugging ;developest optionnel)xdebug.start_with_request=trigger(recommandé) ouyes(plus simple, plus lourd)xdebug.client_host=...(IP/DNS de la machine qui exécute VS Code)xdebug.client_port=9003(port par défaut Xdebug 3)
Exemple (fichier dédié type 99-xdebug.ini chargé par PHP) :
; Xdebug 3.x
xdebug.mode=debug,develop
xdebug.start_with_request=trigger
xdebug.client_host=127.0.0.1
xdebug.client_port=9003
xdebug.log=/tmp/xdebug.log
xdebug.log_level=7
Trois points non négociables :
1) Évitez start_with_request=yes en continu. En environnement web, ça force Xdebug à tenter une connexion IDE à chaque requête (y compris les assets, webhooks, healthchecks, crawlers internes). En pratique, ça peut saturer un pool PHP‑FPM et provoquer des erreurs 502/503 en dev.
2) Activez les logs quand ça ne marche pas. xdebug.log + xdebug.log_level=7 donne des infos actionnables : résolution DNS, tentative de connexion, erreur de socket, IDE absent… Une fois le problème réglé, redescendez le niveau (ou désactivez le log), car le fichier peut grossir vite.
3) Ne confondez pas “client host” et “serveur web”. xdebug.client_host doit pointer vers l’IDE (votre poste), pas vers le serveur PHP. En local sans conteneur, 127.0.0.1 suffit. En Docker/VM, c’est rarement vrai.
Activer Xdebug uniquement “à la demande” (conseil d’équipe)
Deux mécanismes se combinent très bien :
xdebug.start_with_request=trigger(déclenchement explicite)XDEBUG_MODE=debug(variable d’environnement Xdebug 3) pour activer/désactiver sans toucher aux fichiers
Exemple d’approche “hygiène d’équipe” :
- Dans les fichiers ini :
xdebug.mode=off(par défaut). - Quand vous voulez debugger :
XDEBUG_MODE=debug+ trigger.
Déclenchement avec trigger : trois options principales
- Cookie
XDEBUG_TRIGGER=1 - Paramètre GET/POST
XDEBUG_TRIGGER=1 - Variable d’environnement
XDEBUG_TRIGGER=1(utile en CLI)
Documentation (step debugging / triggers) : xdebug.org — Step Debug
Astuce utile en CLI (pour ne pas dépendre du php.ini global) :
XDEBUG_MODE=debug XDEBUG_TRIGGER=1 php -d xdebug.start_with_request=trigger script.php
Configurer Visual Studio Code (launch.json, écoute sur 9003, path mappings)
Côté VS Code, l’objectif est simple : écouter sur 9003 et associer les chemins de fichiers “vus par PHP” à ceux de votre workspace. Le premier point est trivial ; le second est la source n°1 de “breakpoints ignorés”.
Créez/éditez .vscode/launch.json :
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"/var/www/html": "${workspaceFolder}"
},
"xdebugSettings": {
"max_children": 128,
"max_data": 2048,
"max_depth": 5
}
}
]
}
Le mapping pathMappings doit refléter le chemin absolu côté serveur (celui que PHP renvoie à Xdebug) vers le chemin local. Exemple : si votre conteneur monte le projet dans /var/www/html et que VS Code ouvre le repo sur votre machine, c’est exactement ce mapping.
Deux cas qui cassent souvent les breakpoints (sans que la connexion Xdebug soit en cause) :
- Chemins différents selon le point d’entrée : un script est exécuté depuis
/var/www/html/currenten prod-like, mais votre mapping vise/var/www/html. Résultat : VS Code reçoit un chemin qu’il ne sait pas résoudre. - Windows / WSL / chemins sensibles à la casse : si le serveur renvoie
/var/www/Html(ou si vous avez des symlinks), le mapping peut “matcher” côté Linux mais pas côté IDE, ou inversement. Il faut que le chemin renvoyé par PHP corresponde réellement à un fichier du workspace.
Ensuite :
- Lancez la config “Listen for Xdebug” (F5).
- Déclenchez une requête avec
?XDEBUG_TRIGGER=1ou en posant le cookie. - Vérifiez dans l’onglet Debug Console qu’une session est reçue.
Option de diagnostic côté VS Code (quand vous suspectez le mapping) : activez le log du debug adapter le temps de comprendre (dans la configuration, selon version de l’extension, un paramètre log: true peut aider). L’objectif n’est pas de “tout logguer”, mais de voir quels chemins Xdebug annonce réellement.
Docker, VM, WSL, SSH : régler le problème réseau (client_host) proprement
En conteneur Docker, 127.0.0.1 pointe vers… le conteneur, pas votre poste. Vous devez donc donner à Xdebug une route vers l’hôte. Sur Docker Desktop (macOS/Windows), host.docker.internal est généralement disponible :
xdebug.client_host=host.docker.internal
Référence utile (réseau Docker Desktop et nom host.docker.internal) : Docker docs — Networking
Sur Linux, ce DNS n’est pas garanti. Deux approches robustes :
- Utiliser l’IP de la passerelle du bridge Docker (souvent
172.17.0.1) :
xdebug.client_host=172.17.0.1
- Ou injecter dynamiquement l’IP (avancé) pour éviter de figer une valeur qui change selon la machine.
Test rapide depuis le conteneur pour valider que le chemin réseau est bon (adaptable selon l’image) :
# Dans le conteneur (si netcat est disponible)
nc -vz host.docker.internal 9003
# ou
nc -vz 172.17.0.1 9003
Ce test ne garantit pas que les breakpoints tomberont (path mapping…), mais il élimine tout de suite un gros pan des causes.
En VM (VirtualBox/Vagrant/Hyper‑V), même logique : il faut que la VM puisse joindre votre machine sur 9003. Le plus simple est un réseau host‑only ou bridged et xdebug.client_host réglé sur l’IP du poste. Le NAT seul fonctionne rarement sans règles spécifiques.
WSL (points d’attention)
Selon votre setup :
- Si PHP tourne dans WSL2 et VS Code tourne dans Windows,
127.0.0.1côté WSL n’est pas forcément votre Windows hôte. Il faut viser l’IP “hôte” visible depuis WSL (souvent celle du DNS dans/etc/resolv.conf) ou, plus simple, exécuter VS Code dans WSL (Remote WSL), ce qui remet127.0.0.1au bon endroit. - Si vous lancez VS Code attaché à un conteneur (Dev Containers), l’IDE est “dans” le conteneur : le besoin de
client_hostchange (on retombe parfois sur127.0.0.1).
Serveur distant : éviter d’exposer 9003
Deux scénarios :
1) Ouvrir 9003 vers votre poste : souvent impossible en entreprise (firewalls, NAT), et discutable en sécurité.
2) Tunnel SSH reverse (souvent le plus propre) : vous laissez Xdebug “croire” qu’il se connecte localement sur le serveur, et SSH renvoie le flux vers votre VS Code. Exemple :
ssh -R 9003:127.0.0.1:9003 user@serveur
Puis, sur le serveur :
xdebug.client_host=127.0.0.1
xdebug.client_port=9003
Pour les contraintes d’hébergement mutualisé (où SSH reverse n’est pas toujours disponible), vous êtes limité : critères et limites réalistes ici : Hébergement mutualisé — critères techniques
Cas PrestaShop : déboguer modules, hooks, contrôleurs Symfony et CLI
PrestaShop est un terrain propice aux breakpoints “qui ne tombent pas” pour deux raisons : caches (Smarty/OPcache) et multiplicité des points d’entrée (front controller legacy + Symfony dans le back-office). Avant de conclure à un problème Xdebug, invalidez ce qui peut vous jouer des tours : cache Smarty, CCC, caches applicatifs, et surtout la différence entre “fichier source” et “fichier réellement exécuté”.
Pour un rappel orienté performance/caches (à adapter en dev), voir : PrestaShop — Optimiser performance via cache, Smarty & CCC
Mini-scenario réaliste (module + hook)
Vous développez un module qui injecte un JS via hookDisplayHeader, mais votre breakpoint ne se déclenche pas :
- Vous déclenchez l’URL avec
?XDEBUG_TRIGGER=1. - VS Code reçoit bien une session (donc réseau OK).
- Breakpoint gris / “unbound”.
Les 3 causes les plus courantes dans PrestaShop :
- le hook exécuté n’est pas celui que vous pensez (page différente, thème qui n’appelle pas le hook, ou position du module ailleurs) ;
- un override ou une autre classe est chargée (autoloader) : vous debuggez un fichier, mais PHP en exécute un autre ;
- votre mapping pointe vers le repo local, mais le conteneur exécute le code depuis un chemin différent (
/var/www/html/publicvs/var/www/html, release symlink, etc.).
Dans ce type de cas, placez un breakpoint sur un point d’entrée “certain” (ex. le constructeur du module, ou le front controller), puis remontez l’appel. C’est souvent plus rapide que de “deviner le bon hook”.
Debug Back-office Symfony (PrestaShop 8/9)
Quand vous êtes dans le back-office modernisé, déboguer un contrôleur Symfony ou un service est souvent plus déterministe que de partir d’un hook legacy. Autre avantage : les chemins et points d’entrée sont plus standardisés (routing, services), donc les path mappings sont souvent plus simples à stabiliser.
Debug CLI (souvent sous-estimé)
Pour les commandes (cache, index, tâches), le CLI évite les aléas HTTP et donne un flux très reproductible :
XDEBUG_MODE=debug XDEBUG_TRIGGER=1 php -d xdebug.mode=debug bin/console cache:clear
Et si votre objectif est d’analyser des lenteurs SQL plutôt que de comprendre une condition, le pas‑à‑pas n’est pas toujours l’outil le plus rentable : vous pouvez activer le profiling/debug PrestaShop et lire les requêtes ; base utile ici : PrestaShop — Debug & Profiling (analyser performances SQL)
Dépannage : breakpoints ignorés, 502 aléatoires, et hygiène de debug en équipe
Quand “ça ne marche pas”, isolez en 5 minutes avec une checklist brutale :
1) VS Code écoute bien ? (F5, port 9003 ouvert localement)
2) Xdebug est chargé sur le bon SAPI ? (CLI vs FPM)
3) Xdebug tente de se connecter ? (log /tmp/xdebug.log)
4) L’IP/DNS xdebug.client_host est joignable depuis PHP ? (test de connectivité)
5) Les chemins remontés par PHP matchent votre workspace ? (pathMappings)
Table “symptôme → cause probable → action” (pratique quand vous dépannez quelqu’un à distance) :
| Symptôme | Cause probable | Action rapide |
|---|---|---|
| VS Code ne reçoit aucune session | client_host/réseau/port bloqué |
Lire xdebug.log, tester nc -vz <host> 9003, corriger client_host |
| Session reçue mais breakpoints ignorés | pathMappings faux, code exécuté ailleurs (override, symlink, volume) |
Vérifier le chemin renvoyé par Xdebug, ajuster mapping, vérifier overrides |
| Page très lente dès que Xdebug est actif | start_with_request=yes + IDE pas prêt, ou pages lourdes |
Passer en trigger, ne déclencher que sur la page cible |
| 502/503 aléatoires en dev | Workers FPM bloqués en attente du debug | Réduire les triggers, augmenter temporairement le pool, désactiver Xdebug par défaut |
Les erreurs les plus fréquentes côté web :
- Breakpoints non atteints : mapping incorrect, code différent (override), OPcache non invalidé, fichier généré (cache Smarty) vs source.
- Timeouts / lenteurs :
start_with_request=yesen continu + IDE pas prêt (Xdebug tente et retente), ou pages lourdes PrestaShop où le step debugging multiplie le coût. - 502/503 en dev : FPM saturé parce que chaque worker attend l’IDE. Si vous déclenchez le debug “sur tout”, vous bloquez votre pool. Pour garder des réflexes serveurs (logs + ressources) : Erreur HTTP 503 — Diagnostic serveur, logs et ressources
En équipe, imposez une hygiène simple :
- Xdebug désactivé par défaut (ou
xdebug.mode=off). - Activation explicite via variables d’environnement (
XDEBUG_MODE=debug) +start_with_request=trigger. - Un guide court “où mettre
client_hostselon l’environnement” (Docker Desktop, Linux, VM, WSL) pour éviter les setups divergents.
Enfin, ne mélangez pas débogage interactif et observabilité. Le premier sert à comprendre un état précis à un instant T ; le second sert à détecter et corréler en continu. En PrestaShop, si votre besoin est “comprendre pourquoi ça part en erreur en prod”, vous serez plus efficace avec un pipeline de logs/alerting (PHP‑FPM, nginx, MySQL, JS) et des notifications. Base saine ici : PrestaShop — Monitoring, logs et alertes
