Table des matières :
- Pré‑requis réalistes (et ce qui casse le plus souvent)
- Modèle mental : qui se connecte à qui, et pourquoi le port n’est pas “un détail”
- Installer Xdebug (Debian/Ubuntu, Docker, et pièges PHP-FPM)
- Configurer Xdebug 3 : un xdebug.ini minimal (et un mode “trigger” pour ne pas ruiner les perfs)
- Configurer VS Code : extension, launch.json, et mapping de chemins (le vrai “pas à pas”)
- Cas PrestaShop : où placer les breakpoints (FO, BO Symfony, modules, AJAX)
- Docker/VM/Remote : client_host, NAT, et tunnel SSH (debug “propre” sans exposer 9003)
- Check-list de dépannage (breakpoints ignorés, timeouts, chemins faux, opcache)
- Bonnes pratiques : garder Xdebug utile sans transformer votre environnement en boulet
Pré‑requis réalistes (et ce qui casse le plus souvent)
Xdebug + VS Code fonctionne très bien… à condition d’être strict sur les versions et sur le modèle d’exécution PHP. Les exemples ci‑dessous sont alignés avec des stacks courantes en e‑commerce : PHP 8.2/8.3, Xdebug 3.3+, VS Code récent, et des runtimes PHP-FPM/Nginx, Apache mod_php, ou Docker Compose. Si vous êtes sur PrestaShop 9 (Symfony 6.4 / PHP 8.3), restez dans ce périmètre : c’est la base la plus cohérente en 2026 pour éviter des comportements “magiques” (autoloading, cache, opcache) qui masquent les bugs.
Prérequis côté poste :
- VS Code + extension PHP Debug (package
xdebug.php-debug). - Accès au code source local (workspace) strictement identique à ce qui tourne côté PHP (mêmes chemins, même commit).
- Un navigateur (ou un client HTTP) pour déclencher des requêtes web ; optionnel : une extension type “Xdebug helper” pour gérer le trigger.
- Un port local disponible (par défaut 9003) et VS Code autorisé par le firewall (Windows Defender, pare-feu d’entreprise, etc.).
Prérequis côté serveur/conteneur :
- Xdebug installé et chargé comme extension Zend (
zend_extension=xdebug). - Redémarrage effectif du runtime (PHP-FPM/Apache) après modification de la conf.
- Un réseau qui permet au runtime PHP d’initier une connexion TCP vers votre VS Code (c’est toujours le runtime qui “appelle” l’IDE).
Deux vérifications “bêtes” qui évitent de perdre 30 minutes :
1) Vérifier que Xdebug est chargé dans la SAPI qui vous intéresse (CLI et Web n’ont pas toujours la même conf) :
php -v
php -m | grep -i xdebug || true
php --ini
2) Vérifier que le Xdebug du Web est bien actif : créez un phpinfo() temporaire (à retirer ensuite) ou vérifiez la sortie d’un endpoint interne. Beaucoup de “ça marche en CLI mais pas en HTTP” viennent simplement d’un redémarrage oublié de PHP-FPM ou d’un pool FPM différent.
Pour les environnements PrestaShop, gardez en tête que le cœur peut produire des erreurs “silencieuses” (white screen, 500 générique) quand l’affichage des erreurs est coupé. Avant même le debug interactif, vous devez être capable de diagnostiquer une fatale : voir PrestaShop page blanche : diagnostiquer l’erreur fatale PHP rapidement.
Modèle mental : qui se connecte à qui, et pourquoi le port n’est pas “un détail”
Xdebug n’est pas “VS Code qui se connecte au serveur”. C’est l’inverse : le process PHP (FPM worker, Apache child, CLI) ouvre une connexion sortante vers un client DBGp (votre IDE). Ce point explique 80% des échecs en Docker/VM/remote : votre PHP ne peut pas joindre votre machine, ou joint la mauvaise IP.
Le protocole parlé est DBGp. La documentation officielle Xdebug rappelle que l’extension apporte des capacités de débogage et de profiling, et que le débogage interactif passe par un échange réseau entre Xdebug et l’IDE : https://xdebug.org/docs/
Depuis Xdebug 3, le port par défaut est 9003 (et plus 9000, souvent déjà pris par PHP-FPM, Supervisor, ou d’autres services). C’est indiqué dans le guide de migration Xdebug 3 : https://xdebug.org/docs/upgrade_guide
Concrètement, gardez ce “film” en tête :
- Vous démarrez VS Code en listen (il ouvre un port local et attend).
- Vous déclenchez une requête (web ou CLI).
- Xdebug, si activé et déclenché, se connecte à
xdebug.client_host:xdebug.client_port. - VS Code accepte la session, mappe les chemins, puis vos breakpoints deviennent “résolus”.
Mini-schéma mental (très utile en Docker) :
- PHP dans un conteneur → veut joindre VS Code sur l’hôte
127.0.0.1dans le conteneur = le conteneur lui‑même (donc, mauvais client_host dans 90% des cas)- vous devez donc fournir une adresse atteignable depuis le conteneur (
host.docker.internal, IP de passerelle, tunnel, etc.)
Installer Xdebug (Debian/Ubuntu, Docker, et pièges PHP-FPM)
Sur une VM/Debian/Ubuntu avec PHP packagé, le plus simple est d’installer Xdebug via le gestionnaire de paquets (ou via PECL si vous contrôlez votre compilation). Exemple Debian/Ubuntu :
sudo apt update
sudo apt install php-xdebug
php -v | head
Vous devez voir une ligne du type with Xdebug v3.x.x. Ensuite, localisez le fichier INI réellement chargé. Sur Debian, c’est généralement /etc/php/8.3/mods-available/xdebug.ini puis un lien dans conf.d/. Vérifiez côté FPM :
php --ini
sudo systemctl restart php8.3-fpm
Si vous êtes derrière Nginx + PHP-FPM, ne confondez pas php (CLI) et php-fpm (SAPI FPM). C’est un classique : vous “voyez” Xdebug en CLI mais pas en web.
Astuce pratique quand vous avez plusieurs versions de PHP installées (cas fréquent sur des postes de dev ou des serveurs mutualisés “aménagés”) : vérifiez la version exacte de FPM qui tourne et son chemin de conf.
ps aux | grep -E "php-fpm|php[0-9]\.[0-9]-fpm" | head
sudo systemctl status php8.3-fpm --no-pager
Pour comprendre ce flux et les points de rupture (socket FPM, permissions, pools), relisez PHP-FPM : comprendre le flux d’exécution avec Nginx et Apache.
En Docker, l’installation se fait souvent via PECL dans le Dockerfile (image php:fpm), puis activation :
RUN pecl install xdebug \
&& docker-php-ext-enable xdebug
Deux pièges Docker à connaître :
- Si vous rebuild l’image mais ne recréez pas le conteneur, vous pouvez croire que Xdebug est installé alors que vous tournez encore sur un ancien conteneur.
- Si vous montez un
php.ini/conf.ddepuis l’hôte, il peut écraser la conf générée dans l’image (et donc désactiver Xdebug “sans bruit”).
Le piège n°1 en Docker n’est pas l’installation, c’est l’adressage : localhost dans le conteneur ne pointe pas vers votre machine hôte. Sur macOS/Windows, host.docker.internal est généralement disponible. Sur Linux, il faut souvent ajouter extra_hosts (ou utiliser l’IP de la gateway du bridge). Dans une approche “reproductible”, documentez et versionnez ça dans votre docker-compose.yml (voir aussi Staging PrestaShop : créer un environnement Docker fidèle à la production).
Configurer Xdebug 3 : un xdebug.ini minimal (et un mode “trigger” pour ne pas ruiner les perfs)
Pour Xdebug 3, la configuration utile se résume à quelques directives. Exemple minimal (FPM ou Apache) :
; /etc/php/8.3/mods-available/xdebug.ini (exemple)
zend_extension=xdebug
xdebug.mode=debug
xdebug.client_port=9003
xdebug.client_host=127.0.0.1
xdebug.start_with_request=trigger
; utile pour diagnostiquer les problèmes de connexion
xdebug.log=/tmp/xdebug.log
xdebug.log_level=7
Point important : xdebug.start_with_request. Mettre yes force le debug à chaque requête, ce qui est rarement acceptable sur une boutique (même en dev) parce que vous ajoutez une latence réseau + un handshake DBGp à chaque hit, y compris assets/cron/AJAX. En pratique, le bon compromis est trigger : vous activez le debug uniquement quand vous envoyez un paramètre/entête/cookie de trigger.
Avec Xdebug 3, le trigger s’appelle XDEBUG_TRIGGER. Exemple en HTTP :
- query string :
?XDEBUG_TRIGGER=1 - cookie :
XDEBUG_TRIGGER=1 - header :
X-DEBUG-TRIGGER: 1(si votre client le supporte)
Deux raffinements utiles en équipe (sans complexifier) :
- Limiter le trigger à une valeur attendue via
xdebug.trigger_value(pratique en staging partagé : même si quelqu’un metXDEBUG_TRIGGER=1, rien ne se passe sans la valeur convenue). - Éviter
xdebug.client_host=127.0.0.1en conteneur : mettez plutôthost.docker.internal(ou une IP) dans l’environnement concerné.
Table “mémo” (ce qui vaut le coup de connaître) :
| Directive | Valeur conseillée en dev | Pourquoi |
|---|---|---|
xdebug.mode |
debug (évent. debug,develop) |
debug pour l’IDE ; develop pour des erreurs plus lisibles hors IDE |
xdebug.start_with_request |
trigger |
debug à la demande, évite de plomber toutes les requêtes |
xdebug.client_port |
9003 |
port par défaut Xdebug 3, évite les collisions historiques de 9000 |
xdebug.log + log_level |
temporaire | indispensable tant que ça ne se connecte pas, à désactiver ensuite |
Ne sous-estimez pas le logging : pendant la mise au point, xdebug.log vous dit exactement quelle IP/port Xdebug essaie de joindre, et pourquoi la connexion échoue (timeout, refused, DNS). Une fois stable, baissez log_level voire supprimez le log (et pensez aux droits d’écriture de /tmp si vous êtes dans un conteneur durci).
Configurer VS Code : extension, launch.json, et mapping de chemins (le vrai “pas à pas”)
Côté VS Code, installez l’extension PHP Debug (xdebug.php-debug). Référence : https://github.com/xdebug/vscode-php-debug
Vous pouvez aussi garder en favori la documentation générale de VS Code sur le débogage (utile pour comprendre l’UI, les watch, la pile d’appels, etc.) : https://code.visualstudio.com/docs/editor/debugging
Ensuite, créez .vscode/launch.json dans votre workspace. Configuration de base (écoute sur 9003) :
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"log": false
}
]
}
Pas à pas “sans supposer que tout est OK” :
1) Mettez un breakpoint dans un fichier certain d’être exécuté (sur PrestaShop FO : index.php ou un contrôleur). 2) Dans VS Code : onglet “Run and Debug” → lancez Listen for Xdebug. 3) Déclenchez une requête avec le trigger (ex : ?XDEBUG_TRIGGER=1). 4) Si ça ne s’arrête pas : activez temporairement les logs côté Xdebug (xdebug.log_level=7) et côté extension ("log": true) pour voir si vous avez un souci de réseau ou un souci de mapping.
Le point critique est le path mapping quand PHP tourne dans un conteneur/VM. Si vos fichiers sont sous /var/www/html dans le conteneur, mais dans ${workspaceFolder} sur votre poste, ajoutez :
{
"name": "Listen for Xdebug (Docker)",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"/var/www/html": "${workspaceFolder}"
}
}
Sans mapping correct, VS Code reçoit bien la session… mais les breakpoints restent “unverified” car le chemin déclaré par Xdebug ne correspond à aucun fichier local.
Conseils concrets si vous avez une base PrestaShop “réaliste” (thème, modules, vendor) :
- Évitez les montages partiels qui désynchronisent le code : par exemple, si
vendor/est installé dans le conteneur mais pas sur l’hôte, vos stacks traces référenceront des chemins impossibles à ouvrir localement. - Si vous avez un monorepo (plusieurs apps/services), n’hésitez pas à mettre plusieurs entrées dans
pathMappings(une par racine réellement exécutée).
Cas PrestaShop : où placer les breakpoints (FO, BO Symfony, modules, AJAX)
Sur PrestaShop, la plupart des bugs qui justifient Xdebug ne sont pas “dans une fonction isolée”, mais dans une chaîne d’exécution : front controller → dispatcher → module hooks → templates → services Symfony (BO) → Doctrine/DBAL.
Pour du FO, commencer par index.php ou classes/Dispatcher.php peut être utile si vous suspectez une réécriture d’URL ou un contrôleur module mal résolu. Ensuite, descendez “au plus près” du symptôme :
- bug d’affichage : breakpoint dans le contrôleur (ou dans le hook qui assigne des variables Smarty)
- bug de panier : breakpoint côté
Cart/CartControlleret dans les hooks modules liés au checkout - bug de thème : breakpoint dans le module/hook qui injecte l’asset ou la variable, plutôt que dans le template
Pour le Back‑Office moderne (PrestaShop 8+ et surtout 9), beaucoup d’actions passent par des routes Symfony. Si vous travaillez sur un écran BO et que vous ne comprenez pas quel contrôleur est réellement appelé (remap YAML, surcharge, legacy controller), vous pouvez mettre un breakpoint dans le contrôleur Symfony ciblé et remonter. Sur le sujet “routes BO”, vous avez aussi Contrôleur Back Office PrestaShop : remapper une route YAML sans changer l’URL.
Cas AJAX (très fréquent en BO) : vous cliquez sur un bouton, rien ne se passe, et vous avez juste une 500 en arrière-plan.
- Mettez un breakpoint dans le contrôleur Symfony (ou legacy) censé gérer l’action.
- Déclenchez l’appel en gardant
XDEBUG_TRIGGER=1(sur l’URL, ou via cookie), puis regardez la requête dans l’onglet “Network” du navigateur pour confirmer l’endpoint réellement appelé. - Si vous ne tombez jamais sur le breakpoint, c’est souvent que l’action est routée ailleurs (autre contrôleur, autre module) ou qu’un cache reverse-proxy vous sert une réponse ancienne.
Cas module (très fréquent) : vous avez un hook qui plante en production mais pas en dev. Exemple : breakpoint dans modules/votre_module/votre_module.php sur hookActionFrontControllerSetMedia() ou hookDisplayHeader(). Activez ensuite le trigger :
- ouvrez VS Code → lancez “Listen for Xdebug”
- chargez une page FO avec
?XDEBUG_TRIGGER=1 - vérifiez que votre breakpoint est atteint avant le rendu du thème
Astuce CLI utile quand vous déboguez une commande Symfony (indexation, import, cron) : vous pouvez déclencher Xdebug sans HTTP.
XDEBUG_TRIGGER=1 php -d xdebug.mode=debug bin/console cache:clear
Si vous êtes en train de migrer vers PrestaShop 9, prenez en compte que la pile Symfony 6.4 et les évolutions d’autoloading/services changent l’ordre d’initialisation : voir PrestaShop 9 : guide de migration Symfony 6.4 et PHP 8.3.
Docker/VM/Remote : client_host, NAT, et tunnel SSH (debug “propre” sans exposer 9003)
En local Docker, la config la plus robuste consiste à fixer xdebug.client_host vers l’hôte, plutôt que d’activer la découverte automatique. xdebug.discover_client_host=1 marche parfois, mais dépend des entêtes réseau (et peut casser derrière un reverse proxy). En CI ou sur des postes hétérogènes, la reproductibilité prime.
Exemple Docker Compose (Linux) :
services:
php:
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
XDEBUG_MODE: debug
XDEBUG_CONFIG: client_host=host.docker.internal client_port=9003
Avantage : vous ne touchez pas forcément à des INI selon les environnements. Xdebug supporte XDEBUG_MODE pour activer/éteindre des modes au runtime. C’est aussi un bon garde‑fou : par défaut, vous pouvez laisser xdebug.mode=off dans l’image et n’activer le debug que quand c’est nécessaire.
Sur VM/remote, ajoutez deux précautions “terrain” :
- Certains hébergements (mutualisés, ou environnements d’entreprise) filtrent les connexions sortantes : Xdebug peut être parfaitement configuré mais ne jamais réussir à joindre votre poste. Le log Xdebug vous aidera à distinguer “mauvaise IP” vs “timeout réseau”.
- Si vous êtes derrière un VPN, l’IP de votre poste peut changer ; un tunnel SSH est souvent plus stable que de courir après l’IP.
En remote (staging ou serveur mutualisé), n’exposez pas le port 9003 sur Internet. Faites un tunnel SSH inversé ou direct selon le sens de connexion. Le besoin : permettre au serveur de joindre votre IDE. Un modèle courant : tunnel depuis votre poste vers le serveur, puis configuration Xdebug pour pointer sur 127.0.0.1 côté serveur (qui sera tunnelé). Exemple :
ssh -R 9003:localhost:9003 user@serveur
Puis côté serveur : xdebug.client_host=127.0.0.1 et xdebug.client_port=9003. Ça évite d’ouvrir un port entrant et réduit fortement la surface d’attaque.
Variante si le port 9003 est déjà pris côté serveur (ou si vous préférez éviter un conflit) : montez un port de tunnel différent, puis ajustez xdebug.client_port.
ssh -R 19003:localhost:9003 user@serveur
Check-list de dépannage (breakpoints ignorés, timeouts, chemins faux, opcache)
Si VS Code “écoute” mais ne reçoit rien : regardez /tmp/xdebug.log. Vous verrez typiquement Could not connect to client avec l’IP/port tenté. Dans Docker, c’est souvent client_host=127.0.0.1 (mauvais), ou une résolution DNS manquante de host.docker.internal. Sur Linux, la correction passe quasi toujours par extra_hosts: host-gateway.
Commandes simples pour objectiver (au lieu de “ça devrait marcher”) :
# côté serveur / conteneur : lire la conf effective
php -i | grep -E "xdebug\.mode|xdebug\.client_host|xdebug\.client_port|xdebug\.start_with_request"
# côté poste : vérifier que VS Code écoute bien
# (Linux)
ss -ltnp | grep 9003 || true
Si VS Code reçoit une connexion mais que les breakpoints restent gris : c’est du path mapping. Vérifiez le chemin exact remonté par Xdebug (dans l’onglet Debug Console avec log, ou dans le log Xdebug). Assurez‑vous que le fichier local existe au même chemin mappé et que c’est le même code (pas un vendor différent, pas un volume partiel). Dans PrestaShop, les overrides et le cache de classes peuvent aussi vous faire breakpoint sur un fichier qui n’est plus exécuté.
Si les breakpoints sont atteints “une fois sur deux” : suspectez OPcache et le cycle de vie FPM. OPcache peut garder des scripts en mémoire, et selon vos réglages, un container rebuild/restart est nécessaire. Sur des stacks perf (Redis/Varnish/CDN), vous pouvez aussi être trompé par des caches applicatifs : relisez votre stratégie de cache dev/staging si besoin (ex : Cache PrestaShop : stratégie 2026 OPcache, Redis, Varnish et CDN).
Table de diagnostic rapide (symptôme → cause probable → action) :
| Symptôme | Cause probable | Action courte |
|---|---|---|
| VS Code n’affiche aucune session | Xdebug non chargé côté Web, ou Xdebug ne tente pas de connexion | Vérifier phpinfo() / redémarrer PHP-FPM / lire xdebug.log |
Could not connect to client |
Mauvais client_host / port bloqué / DNS |
En Docker, préférer host.docker.internal + extra_hosts |
| Session reçue mais breakpoints “unverified” | pathMappings faux |
Mapper le chemin exact du conteneur (/var/www/...) vers ${workspaceFolder} |
| Breakpoints OK en CLI mais pas en HTTP | Conf CLI ≠ conf FPM/Apache | Modifier le bon INI (FPM), redémarrer le service |
| Code modifié mais pas pris en compte | OPcache / cache PrestaShop / conteneur non recréé | Restart FPM / vider var/cache / rebuild-recreate |
Enfin, si votre objectif initial est “juste comprendre une 500”, Xdebug n’est pas toujours le premier outil : activer les logs PHP-FPM/Nginx et reproduire proprement est souvent plus rapide. Sur infra OVH, certaines contraintes (versions PHP, modes d’exécution) se règlent au niveau .ovhconfig : OVHcloud .ovhconfig : changer version PHP, moteur et mode d’exécution.
Bonnes pratiques : garder Xdebug utile sans transformer votre environnement en boulet
Xdebug est excellent pour l’inspection d’état, mais c’est un coût : plus de latence, plus de mémoire, et parfois des effets de bord (timeouts, comportement différent sous charge). En dev, utilisez start_with_request=trigger et un outil de trigger (cookie/paramètre) pour cibler une seule requête. En staging partagé, évitez le debug permanent : vous allez dégrader les tests des autres.
Séparez les modes : debug pour l’interactif, develop pour des informations de stack traces améliorées, profile/trace pour l’analyse de performance. Ne mélangez pas “debugger un bug fonctionnel” et “profiler une lenteur” dans la même session, sinon vous interprétez des timings biaisés. Pour les lenteurs, partez plutôt d’un pipeline d’observabilité (logs, métriques) et n’utilisez le profiling Xdebug que pour confirmer un hotspot.
Deux pratiques qui rendent un setup d’équipe beaucoup plus stable :
- Ne pas embarquer Xdebug “toujours ON” dans l’image/VM : laissez
xdebug.mode=offpar défaut et activez ponctuellement viaXDEBUG_MODE=debug(surtout si vous générez des environnements éphémères). - Sécuriser le déclenchement (staging) : gardez
triggeret utilisez unetrigger_valuepartagée uniquement par l’équipe, plutôt que de laisser n’importe quel visiteur déclencher un debug.
Dernier point : versionnez la config VS Code mais restez pragmatique. launch.json peut être commité (utile en équipe), mais les IP/hostnames doivent être paramétrables. Une approche propre : valeurs par défaut génériques + surcharges via variables d’environnement (XDEBUG_CONFIG, XDEBUG_MODE) ou via un fichier non commité (.env.local). Ça évite les “ça marche sur ma machine” et rend vos sessions de debug réellement reproductibles, y compris quand vous mettez en place de la CI/CD pour des démos éphémères (voir PrestaShop CI/CD : générer des démos à la demande en minutes).




