XDEBUG_MODE, Path-Mapping und Remote-Interpreter
Das Debugging von PHPUnit-Tests in einer Docker-Umgebung mit PhpStorm ist einer der frustrierendsten Punkte in der PHP-Entwicklung. Die Konfiguration scheitert oft an XDEBUG_MODE, falschem Path-Mapping oder einem nicht korrekt konfigurierten Remote-Interpreter. Diese Anleitung zeigt den vollständigen Weg – von der Xdebug-Installation bis zu laufenden Coverage-Reports.
Inhaltsverzeichnis
- 1. Xdebug 3 vs. Xdebug 2: Was sich geändert hat
- 2. Xdebug in Docker korrekt installieren und konfigurieren
- 3. XDEBUG_MODE: Der häufigste Konfigurationsfehler
- 4. PhpStorm Remote-Interpreter konfigurieren
- 5. Path-Mapping: Container-Pfade und lokale Pfade verbinden
- 6. PHPUnit-Tests direkt aus PhpStorm ausführen
- 7. Coverage-Reports in PhpStorm anzeigen
- 8. Diagnose: Wenn das Debugging nicht funktioniert
- 9. PCOV vs. Xdebug für Coverage im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Xdebug 3 vs. Xdebug 2: Was sich geändert hat
Xdebug 3 hat das Konfigurationsmodell von Xdebug 2 grundlegend geändert. In Xdebug 2 wurden Debugging, Profiling und Coverage über separate INI-Direktiven aktiviert (xdebug.remote_enable=1, xdebug.coverage_enable=1). In Xdebug 3 wird ein einziges xdebug.mode-Feld gesetzt, das alle Funktionen kontrolliert. Die Modi sind: debug für Step-Debugging, coverage für Code-Coverage, profile für Profiling und trace für Tracing. Mehrere Modi werden kommasepariert angegeben: debug,coverage.
Der wichtigste Unterschied für Docker-Umgebungen: In Xdebug 3 kann der Modus über die Umgebungsvariable XDEBUG_MODE gesetzt werden, ohne die php.ini zu ändern. Das ermöglicht es, Xdebug standardmäßig deaktiviert (XDEBUG_MODE=off) zu halten und nur für spezifische Kommandos zu aktivieren. Das ist erheblich besser als das Xdebug-2-Modell, wo Xdebug entweder global aktiv oder global inaktiv war – ein Hauptgrund für die Performanceprobleme, die viele Entwickler früher mit Xdebug in Docker hatten.
2. Xdebug in Docker korrekt installieren und konfigurieren
Im Mark-Shust-Docker-Setup für Magento wird Xdebug über das Wrapper-Script bin/xdebug enable|disable ein- und ausgeschaltet. Intern schaltet dieses Script die xdebug.ini im PHP-Konfigurationsverzeichnis des Containers um. Für eine manuelle Installation in einem anderen Docker-Setup ist der Ansatz über ein separates INI-Snippet zu bevorzugen, das nur Xdebug-spezifische Konfiguration enthält und unabhängig von der Haupt-php.ini versioniert werden kann.
; /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
; Xdebug 3 configuration for Docker development environment
[xdebug]
zend_extension=xdebug
; Default mode: off — activated via XDEBUG_MODE environment variable
xdebug.mode=off
; Client host: host.docker.internal resolves to the Docker host on Mac/Windows
; On Linux: use the gateway IP (usually 172.17.0.1) or host-gateway in compose
xdebug.client_host=host.docker.internal
xdebug.client_port=9003
; Disable discovery — PhpStorm will connect automatically on breakpoint
xdebug.discover_client_host=false
xdebug.start_with_request=yes
; IDE Key must match PhpStorm's DBGp Proxy settings
xdebug.idekey=PHPSTORM
; Log to stderr for easy debugging of connection issues
; xdebug.log=/var/log/xdebug.log
; xdebug.log_level=7
Für Linux-Hosts gibt es eine Besonderheit: host.docker.internal ist auf Linux nicht automatisch verfügbar (anders als auf macOS und Windows). Die Lösung in Docker Compose: extra_hosts: - "host.docker.internal:host-gateway" im Service-Block des PHP-Containers. Ab Docker 20.10 funktioniert host-gateway auf allen Plattformen und löst automatisch zur korrekten Host-IP auf. Alternativ kann die IP des Docker-Gateways (172.17.0.1 oder 172.16.0.1) direkt eingetragen werden.
3. XDEBUG_MODE: Der häufigste Konfigurationsfehler
Der häufigste Fehler bei Xdebug 3 in Docker: Die xdebug.mode=off-Einstellung in der INI-Datei verhindert jedes Debugging und Coverage, auch wenn Xdebug geladen ist. Gleichzeitig vergessen viele Entwickler, XDEBUG_MODE zu setzen, wenn sie PHPUnit ausführen. Das Ergebnis: Keine Breakpoints, keine Coverage, kein Fehler – nur Stille. PHPUnit läuft, aber Xdebug tut nichts.
Die korrekte Vorgehensweise: XDEBUG_MODE wird für jeden Anwendungsfall explizit gesetzt. Für Step-Debugging: XDEBUG_MODE=debug vendor/bin/phpunit. Für Coverage: XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html reports/coverage. Für beides: XDEBUG_MODE=debug,coverage vendor/bin/phpunit. Die Umgebungsvariable überschreibt den INI-Wert und ermöglicht granulare Kontrolle ohne INI-Änderungen.
# docker-compose.yml — PHP service with Xdebug configuration
services:
phpfpm:
image: markoshust/magento-php:8.4-fpm
volumes:
- ./src:/var/www/html
environment:
# Default: off — enable only when needed
XDEBUG_MODE: "off"
extra_hosts:
- "host.docker.internal:host-gateway" # Linux compatibility
# .env (project root) — override per developer machine
# Uncomment to enable debugging globally (use sparingly)
# XDEBUG_MODE=debug
# bin/xdebug wrapper script — enables/disables per-request
#!/usr/bin/env bash
# Usage: bin/xdebug enable | disable
if [[ "$1" == "enable" ]]; then
bin/cli bash -c "echo 'xdebug.mode=debug,develop' > /usr/local/etc/php/conf.d/xdebug-mode.ini"
bin/restart
elif [[ "$1" == "disable" ]]; then
bin/cli bash -c "echo 'xdebug.mode=off' > /usr/local/etc/php/conf.d/xdebug-mode.ini"
bin/restart
fi
# Run PHPUnit with coverage — no persistent Xdebug activation needed
bin/cli bash -c "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html reports/coverage"
4. PhpStorm Remote-Interpreter konfigurieren
PhpStorm muss wissen, welcher PHP-Interpreter für Tests verwendet werden soll. In einer Docker-Umgebung ist das der PHP-Interpreter innerhalb des Containers – nicht der lokal installierte PHP-Interpreter. Die Konfiguration erfolgt unter Settings → PHP → CLI Interpreter. Dort wird ein neuer Interpreter vom Typ "Docker" oder "Docker Compose" hinzugefügt. PhpStorm verbindet sich dann über das Docker-API mit dem Container und führt PHP-Befehle innerhalb des Containers aus.
Das kritische Detail: PhpStorm übersetzt Dateipfade automatisch zwischen dem lokalen Filesystem (wo der Code auf dem Host liegt) und dem Container-Filesystem (wo PHP den Code tatsächlich ausführt). Dieses Path-Mapping muss korrekt konfiguriert sein, damit Breakpoints in PhpStorm korrekt auf die laufende Codezeile im Container zeigen. Ein falsch konfiguriertes Path-Mapping ist der zweithäufigste Grund, warum Breakpoints in PHPUnit-Tests mit Docker-Remote-Interpreter nicht angesprungen werden.
5. Path-Mapping: Container-Pfade und lokale Pfade verbinden
Beim Path-Mapping ordnet PhpStorm einem lokalen Verzeichnis auf dem Host einen Pfad im Container zu. In einem typischen Magento-Docker-Setup liegt der Magento-Code lokal in ./src und wird im Container unter /var/www/html eingehängt. Das Path-Mapping lautet dann: Lokaler Pfad /home/mir/development/mironsoft/src → Container-Pfad /var/www/html.
Wenn das Mapping fehlt oder falsch ist, öffnet PhpStorm entweder die falsche Datei beim Breakpoint-Hit, oder der Breakpoint wird nie getroffen, weil PhpStorm den Pfad nicht zuordnen kann. Der einfachste Diagnosetest: Ein einfaches PHP-Skript mit einem echo im Container ausführen und prüfen, ob PhpStorm den Durchbruch erkennt. Wenn das klappt, liegt das Problem beim Path-Mapping für PHPUnit-spezifische Dateien.
# PhpStorm Run/Debug Configuration for PHPUnit in Docker
# (Settings → PHP → Test Frameworks → PHPUnit)
# Interpreter: Docker Compose — phpfpm service
# PHPUnit: Use Composer autoloader
# Path to script: /var/www/html/vendor/autoload.php
# Path Mappings in the Interpreter:
# Local path (host): /home/mir/development/mironsoft/src
# Remote path (container): /var/www/html
# .idea/php.xml — stored by PhpStorm, shows the connection
<component name="PhpInterpreters">
<interpreters>
<interpreter id="docker-compose-php84"
name="Docker PHP 8.4"
home="docker-compose://./compose.dev.yaml:phpfpm:/usr/local/bin/php">
<path_mappings>
<path_mapping
local-root="$PROJECT_DIR$/src"
remote-root="/var/www/html" />
</path_mappings>
</interpreter>
</interpreters>
</component>
# Verify Xdebug is available in the correct mode:
# bin/cli php -r "var_dump(xdebug_info());"
# Expected: mode = coverage (or debug, depending on XDEBUG_MODE)
6. PHPUnit-Tests direkt aus PhpStorm ausführen
Nachdem Remote-Interpreter und Path-Mapping korrekt konfiguriert sind, können PHPUnit-Tests direkt aus der PhpStorm-IDE gestartet werden – per Klick auf den grünen Pfeil neben der Testklasse oder über das Run-Menü. PhpStorm generiert intern ein Kommando, das PHPUnit im Container ausführt und die Ausgabe in der IDE anzeigt. Der Vorteil: Testnamen, Fehlermeldungen und Stack-Traces werden direkt in PhpStorm verlinkt und können per Klick zur entsprechenden Codezeile navigieren.
Für die Test-Run-Konfiguration ist das phpunit.xml-Template entscheidend. PhpStorm liest die Datei automatisch aus dem Projektverzeichnis, wenn sie als Standard-Konfigurationsdatei angegeben ist. Die bootstrap-Direktive muss auf den Container-Pfad zeigen (/var/www/html/vendor/autoload.php), nicht auf den lokalen Pfad – PhpStorm übersetzt diese Pfade beim Ausführen automatisch über das Path-Mapping, aber die Konfigurationsdatei selbst wird vom PHP-Prozess im Container gelesen.
7. Coverage-Reports in PhpStorm anzeigen
PhpStorm kann Coverage-Reports direkt im Editor visualisieren: Zeilen werden grün (abgedeckt) oder rot (nicht abgedeckt) markiert, und in der Seitenleiste erscheint ein Coverage-Prozentwert pro Klasse. Diese Funktion erfordert, dass Xdebug im Coverage-Modus läuft (XDEBUG_MODE=coverage) und dass PHPUnit mit der --coverage-php-Option eine PHP-Serialisierung der Coverage-Daten ausgibt.
Die einfachste Methode: In der PHPUnit-Run-Konfiguration in PhpStorm den Haken bei "Collect coverage" setzen. PhpStorm setzt dann automatisch die nötigen PHP-Flags und lädt nach dem Test-Lauf die Coverage-Daten in den Editor. Die Coverage-Daten werden im PhpStorm-Projektvorderzug gecacht und bleiben über Test-Läufe hinweg sichtbar, bis sie durch einen neuen Coverage-Lauf aktualisiert werden.
| Problem | Wahrscheinliche Ursache | Lösung |
|---|---|---|
| Breakpoints werden nicht getroffen | Path-Mapping falsch oder XDEBUG_MODE=off | Path-Mapping prüfen, XDEBUG_MODE=debug setzen |
| Coverage-Datei ist leer | XDEBUG_MODE=coverage fehlt | XDEBUG_MODE=coverage als Env-Variable setzen |
| PHPUnit findet vendor/autoload.php nicht | Bootstrap-Pfad ist Host-Pfad statt Container-Pfad | phpunit.xml bootstrap auf /var/www/html/... setzen |
| host.docker.internal nicht erreichbar | Linux ohne extra_hosts Konfiguration | extra_hosts: host.docker.internal:host-gateway |
| PhpStorm "Waiting for connection" | Firewall blockiert Port 9003 | Firewall-Regel für Port 9003 einrichten |
8. Diagnose: Wenn das Debugging nicht funktioniert
Systematische Diagnose ist bei Xdebug-Problemen wichtiger als Trial-and-Error. Der erste Diagnoseschritt: Prüfen ob Xdebug überhaupt geladen ist: bin/cli php -m | grep xdebug. Wenn Xdebug nicht erscheint, ist die Extension nicht geladen. Wenn Xdebug erscheint, aber nichts passiert: bin/cli php -r "var_dump(xdebug_info());" zeigt den aktuellen Modus und alle Konfigurationswerte.
Der zweite Schritt: Xdebug-Logging aktivieren. Die Direktive xdebug.log=/tmp/xdebug.log und xdebug.log_level=7 schreiben detaillierte Verbindungsinformationen in eine Datei. Das Log zeigt, ob Xdebug versucht, eine Verbindung zu PhpStorm aufzubauen, und ob die Verbindung scheitert. Häufige Fehler: Falsche Client-IP, falscher Port, oder PhpStorm hört nicht auf dem konfigurierten Port. Der PhpStorm-Debugger-Port (standardmäßig 9003) muss in den PhpStorm-Einstellungen aktiviert und nicht durch eine Firewall blockiert sein.
9. PCOV vs. Xdebug für Coverage im Vergleich
Für Code-Coverage gibt es eine leistungsstarke Alternative zu Xdebug: PCOV. PCOV ist ausschließlich für Coverage entwickelt worden und ist erheblich schneller als Xdebug im Coverage-Modus – typischerweise um den Faktor 3–5. PCOV aktiviert kein Step-Debugging und hat keinen Profiling-Modus. Es ist eine spezialisierte PHP-Extension, die nur Code-Coverage-Daten sammelt.
Die Empfehlung für Produktions-CI-Pipelines: PCOV statt Xdebug für Coverage-Jobs. In der lokalen Entwicklung mit PhpStorm ist Xdebug weiterhin notwendig, weil PhpStorm das Xdebug-Protokoll für die Coverage-Visualisierung im Editor nutzt. Eine pragmatische Lösung: Xdebug für die lokale Entwicklung (Debugging + Coverage in PhpStorm), PCOV für CI-Coverage-Jobs (schneller, kein Debugging-Overhead). PHPUnit selbst unterstützt beide Engines und wählt automatisch die verfügbare Engine aus.
10. Zusammenfassung
Xdebug 3 in Docker mit PhpStorm richtig zu verbinden erfordert die korrekte Konfiguration von vier Komponenten: der Xdebug-INI im Container, der Umgebungsvariable XDEBUG_MODE, dem PhpStorm-Remote-Interpreter und dem Path-Mapping. Jede dieser Komponenten muss korrekt sein – ein Fehler an einer Stelle verhindert das gesamte Debugging, ohne dass eine klare Fehlermeldung erscheint.
Die wichtigsten Merksätze: XDEBUG_MODE=off in der INI ist der sinnvolle Standard – Xdebug wird nur für konkrete Kommandos aktiviert. XDEBUG_MODE=coverage ist für Coverage-Reports Pflicht. Path-Mapping muss Host-Pfad zu Container-Pfad korrekt zuordnen. Für Linux-Hosts ist extra_hosts: host.docker.internal:host-gateway in Docker Compose notwendig. PCOV ist für CI-Coverage-Jobs erheblich schneller als Xdebug.
Xdebug und PHPUnit in Docker — Das Wichtigste auf einen Blick
XDEBUG_MODE
Standard: off in der INI. Aktivierung per Umgebungsvariable: XDEBUG_MODE=debug für Step-Debugging, XDEBUG_MODE=coverage für Coverage. Beides: debug,coverage.
Linux host.docker.internal
Auf Linux braucht Docker Compose extra_hosts: - "host.docker.internal:host-gateway" im PHP-Service. Ohne das ist der Debugger-Port nicht erreichbar.
Path-Mapping in PhpStorm
Host-Pfad zu Container-Pfad zuordnen. Typisch: /home/user/project/src → /var/www/html. Ohne korrektes Mapping werden Breakpoints nicht getroffen.
PCOV für CI
PCOV ist 3–5x schneller als Xdebug für Coverage. Kein Debugging-Overhead. Für CI-Coverage-Jobs empfohlen. Lokal mit PhpStorm weiterhin Xdebug verwenden.
11. FAQ: Xdebug und PHPUnit in Docker mit PhpStorm
1Warum werden Breakpoints in PHPUnit-Tests nicht getroffen?
bin/cli php -r "var_dump(xdebug_info());"2Warum ist die Coverage-Datei leer?
XDEBUG_MODE=coverage. Ohne diese Umgebungsvariable sammelt Xdebug keine Coverage-Daten, auch wenn es geladen ist.3host.docker.internal auf Linux konfigurieren?
extra_hosts: - "host.docker.internal:host-gateway". Ab Docker 20.10 auf allen Plattformen verfügbar.4PCOV vs. Xdebug für Coverage?
5PhpStorm Remote-Interpreter für Docker Compose konfigurieren?
6PHPUnit findet vendor/autoload.php nicht?
bootstrap="/var/www/html/vendor/autoload.php". PHPUnit läuft im Container und sieht Host-Pfade nicht.7Xdebug-Logging für die Diagnose aktivieren?
xdebug.log=/tmp/xdebug.log und xdebug.log_level=7 in der INI. Das Log zeigt alle Verbindungsversuche mit Fehlerursachen.8Xdebug standardmäßig deaktivieren?
xdebug.mode=off in der INI. Für konkrete Kommandos: XDEBUG_MODE=debug vendor/bin/phpunit. Kein dauerhafter Performance-Einbruch.9Erste Diagnoseschritte wenn Debugging gar nicht reagiert?
10PHPUnit mit Coverage im Terminal ausführen?
bin/cli bash -c 'XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html reports/coverage/html'. Verzeichnis vorher mit mkdir -p anlegen.