@test
assert
Xdebug 3 · Docker · PhpStorm · PHPUnit · Magento 2 · Coverage
Xdebug und PHPUnit in Docker mit PhpStorm richtig verbinden
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.

25 Min. Lesezeit Xdebug 3 · XDEBUG_MODE · Path-Mapping · Remote-Interpreter · Coverage Docker · PhpStorm 2024+ · PHP 8.4 · Mark Shust Setup

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?
XDEBUG_MODE nicht auf 'debug' gesetzt, falsches Path-Mapping oder PhpStorm hört nicht auf Port 9003. Diagnose: bin/cli php -r "var_dump(xdebug_info());"
2Warum ist die Coverage-Datei leer?
Xdebug 3 benötigt XDEBUG_MODE=coverage. Ohne diese Umgebungsvariable sammelt Xdebug keine Coverage-Daten, auch wenn es geladen ist.
3host.docker.internal auf Linux konfigurieren?
In docker-compose.yml beim PHP-Service: extra_hosts: - "host.docker.internal:host-gateway". Ab Docker 20.10 auf allen Plattformen verfügbar.
4PCOV vs. Xdebug für Coverage?
PCOV ist 3–5x schneller, kein Debugging-Overhead. Xdebug für lokale Entwicklung mit PhpStorm, PCOV für CI-Coverage-Jobs.
5PhpStorm Remote-Interpreter für Docker Compose konfigurieren?
Settings → PHP → CLI Interpreters → + → Docker Compose. Compose-Datei und Service 'phpfpm' angeben. PhpStorm liest PHP-Version und Extensions automatisch.
6PHPUnit findet vendor/autoload.php nicht?
Bootstrap-Direktive in phpunit.xml muss den Container-Pfad angeben: 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?
Empfohlener Ansatz: 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?
1. Xdebug geladen? 2. Modus korrekt? 3. PhpStorm hört auf Port 9003? 4. Firewall blockiert Port? 5. host.docker.internal erreichbar? Alle fünf Punkte systematisch prüfen.
10PHPUnit mit Coverage im Terminal ausführen?
Im Mark-Shust-Setup: bin/cli bash -c 'XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html reports/coverage/html'. Verzeichnis vorher mit mkdir -p anlegen.