Files
Snippets/xdebug-spickzettel.md
T
2026-08-24 19:58:21 +02:00

17 KiB
Raw Blame History

Xdebug – Installation, Konfiguration, Nutzung

Stand: Xdebug 3.x (Modus-Konfiguration ab Xdebug 3, Port 9003)


0. Vorab: Versionsmatrix

PHP Xdebug Konfigurationsstil
7.0 – 7.1 2.9.x xdebug.remote_*, Port 9000
7.2 – 7.4 3.1.x xdebug.mode, Port 9003
8.0 – 8.2 3.2.x xdebug.mode, Port 9003
8.3+ 3.3+ / 3.5+ xdebug.mode, Port 9003

Xdebug 3.5 setzt mindestens PHP 8.3 voraus. Bei älterem PHP die Version explizit pinnen: pecl install xdebug-3.2.2

Verzeichnisnamen wie no-debug-non-zts-20220829 kodieren die PHP-API-Version: 20220829 = PHP 8.2, 20230831 = PHP 8.3, 20240924 = PHP 8.4.


1. Installation unter XAMPP (macOS)

Xdebug wird für macOS nicht vorkompiliert ausgeliefert – es muss gebaut werden.

1.1 Vorbereitung

xcode-select --install     # Compiler / Command Line Tools
brew install autoconf      # phpize benötigt autoconf

Auf Apple Silicon zusätzlich:

softwareupdate --install-rosetta --agree-to-license

1.2 Architektur-Falle (Apple Silicon)

XAMPP für macOS ist x86_64 und läuft unter Rosetta 2. Ein nativer Build auf einem M-Mac erzeugt eine arm64-.so, die PHP nicht laden kann:

dlopen(...) (mach-o file, but is an incompatible architecture
            (have 'arm64', need 'x86_64'))

arch -x86_64 vor pecl reicht nicht – pecl ist ein PHP-Skript, dessen Kindprozesse (phpize, configure, clang) die Rosetta-Umgebung nicht zuverlässig erben. Deshalb eine komplette x86_64-Shell öffnen:

arch -x86_64 /bin/zsh
arch                       # muss "i386" ausgeben

1.3 Build via pecl

# Alte Reste entfernen, sonst wird der falsche Build wiederverwendet
sudo rm -f /Applications/XAMPP/xamppfiles/lib/php/extensions/no-debug-non-zts-*/xdebug.so
rm -rf /tmp/pear

# PEAR-Registry abmelden (sonst: "already installed ... install failed")
sudo /Applications/XAMPP/xamppfiles/bin/pecl uninstall xdebug

# Build (in der Rosetta-Shell!)
sudo CFLAGS="-arch x86_64" LDFLAGS="-arch x86_64" CXXFLAGS="-arch x86_64" \
     /Applications/XAMPP/xamppfiles/bin/pecl install xdebug-3.2.2

pecl gibt am Ende die Zeile Installing '/Applications/XAMPP/.../xdebug.so' aus – das ist der Pfad für die php.ini.

1.4 Ergebnis prüfen

file /Applications/XAMPP/xamppfiles/lib/php/extensions/no-debug-non-zts-20220829/xdebug.so
# muss "x86_64" enthalten

1.5 Manueller Build (falls pecl scheitert)

Gibt mehr Kontrolle, weil jeder Schritt einzeln sichtbar ist:

cd /tmp && curl -O https://xdebug.org/files/xdebug-3.2.2.tgz
tar -xzf xdebug-3.2.2.tgz && cd xdebug-3.2.2

/Applications/XAMPP/xamppfiles/bin/phpize
./configure --with-php-config=/Applications/XAMPP/xamppfiles/bin/php-config \
            CFLAGS="-arch x86_64" LDFLAGS="-arch x86_64"
make
file modules/xdebug.so     # VOR dem Kopieren prüfen
sudo cp modules/xdebug.so \
     /Applications/XAMPP/xamppfiles/lib/php/extensions/no-debug-non-zts-20220829/

1.6 XAMPP-VM (macOS)

Bei dieser Variante läuft der Stack in einer Linux-VM; die Mac-Pfade sind nur Mounts. Ein Build auf dem Host bringt nichts. Terminal in der VM öffnen (XAMPP-Fenster → General / Volumes) und dort nach Linux-Verfahren vorgehen.

1.7 Alternative ohne Compile-Schmerz

brew install php           # natives arm64-PHP
pecl install xdebug        # läuft ohne Rosetta durch
php -S localhost:8000      # eingebauter Server statt Apache

2. Installation unter XAMPP (Windows)

Deutlich einfacher – es gibt fertige DLLs.

2.1 Passende DLL ermitteln

XAMPP kompiliert PHP als Thread Safe (Apache mit mod_php), also wird eine TS-DLL benötigt. Statt zu raten, den Xdebug-Wizard nutzen:

https://xdebug.org/wizard

  1. phpinfo()-Ausgabe erzeugen – im Browser (http://localhost/info.php) oder per C:\xampp\php\php.exe -i > info.txt
  2. Kompletten Output kopieren (Strg+A / Strg+C), nicht nur die sichtbare Tabelle
  3. Ins Textfeld einfügen → Analyse my phpinfo() output
  4. Der Wizard nennt exakt die passende Datei, z. B. php_xdebug-3.4.x-8.2-vs16-x86_64.dll

Alternativ manuell unter https://xdebug.org/download – dann Thread Safety und VS/VC-Compilerversion selbst aus phpinfo() ablesen.

Nach dem Test: info.php wieder löschen.

2.2 Installieren

DLL kopieren nach C:\xampp\php\ext\php_xdebug.dll, dann in C:\xampp\php\php.ini (ganz unten steht bereits ein auskommentierter [XDebug]-Block):

[XDebug]
zend_extension = xdebug
xdebug.mode = develop,debug
xdebug.start_with_request = trigger
xdebug.client_port = 9003
xdebug.client_host = 127.0.0.1

2.3 Prüfen

Apache über das Control Panel Stop/Start (nicht nur „Config"), dann:

C:\xampp\php\php.exe -v

Die Ausgabe muss with Xdebug v3.x.x enthalten.

2.4 Häufige Fehler

Symptom Ursache
Extension wird stillschweigend ignoriert extension= statt zend_extension=
Änderung wirkt nicht falsche php.ini bearbeitet
Architekturfehler x86 statt x64 oder NTS statt TS

3. Installation unter Debian

Der bequemste Fall – Paketverwaltung erledigt alles.

3.1 Über apt

sudo apt install php-xdebug
# oder versionsspezifisch, z. B. bei Sury-Repository:
sudo apt install php8.4-xdebug

3.2 Über pecl (wenn kein Paket verfügbar)

sudo apt install php-pear php-dev autoconf
sudo pecl install xdebug

3.3 Konfiguration – hier gilt conf.d

Debian nutzt ein Scan-Verzeichnis. Nicht die zentrale php.ini anfassen, sondern eine eigene Datei anlegen:

/etc/php/8.4/apache2/conf.d/99-xdebug-local.ini
xdebug.mode = develop,debug
xdebug.start_with_request = trigger
xdebug.client_port = 9003
xdebug.discover_client_host = 1
xdebug.var_display_max_depth = 10
xdebug.var_display_max_data = 1024
xdebug.var_display_max_children = 256

Warum 99-? Die Dateien werden alphabetisch geladen, die letzte gewinnt. Xdebug bringt typischerweise 20-xdebug.ini mit – 99- läuft danach und überschreibt. Außerdem überlebt die eigene Datei ein apt upgrade, während Änderungen an der zentralen php.ini einen Konfliktdialog auslösen.

3.4 SAPI-Trennung beachten

Debian hat getrennte Bäume:

/etc/php/8.4/apache2/conf.d/
/etc/php/8.4/fpm/conf.d/
/etc/php/8.4/cli/conf.d/

Eine Änderung in cli/ wirkt nicht auf den Webserver. Meist sind es Symlinks nach /etc/php/8.4/mods-available/ – dort anlegen und mit phpenmod aktivieren, dann gilt sie für beide.

sudo systemctl restart apache2

4. Wo wird was eingetragen?

4.1 Die entscheidende Frage zuerst

php -i | grep -E "Loaded Configuration|Scan this dir"
Ergebnis bei Scan this dir Vorgehen
ein Verzeichnispfad eigene Datei mit hoher Nummer, z. B. 99-xdebug-local.ini
(none) direkt in die php.ini (Loaded Configuration File)

XAMPP → immer php.ini (Windows, macOS, Linux). XAMPP liefert bewusst eine einzige, selbst verwaltete Datei ohne conf.d. Ausnahme: XAMPP-VM auf macOS – innen läuft ein Debian, dort ggf. doch conf.d. Also in der VM einmal php -i | grep "Scan this dir" prüfen.

Debian / RHEL / Sury / Docker-Images → conf.d mit 99-…

CLI und Browser können unterschiedliche INIs laden. Was Apache tatsächlich nutzt, zeigt nur phpinfo() im Browser.

4.2 Referenz-Konfiguration

zend_extension = xdebug.so          ; NICHT "extension="!

; Was Xdebug tut
xdebug.mode = develop,debug         ; develop = schöneres var_dump + Stack-Traces
                                    ; debug   = Breakpoints
                                    ; trace   = Aufrufprotokoll in Datei
                                    ; profile = Performance-Analyse

; Wann Xdebug aktiv wird
xdebug.start_with_request = trigger ; nur bei ?XDEBUG_TRIGGER=1 – hält Overhead klein

; Verbindung zur IDE
xdebug.client_port = 9003
xdebug.client_host = 127.0.0.1
xdebug.discover_client_host = 1     ; praktisch bei Remote / Lima / Docker

; Anzeige-Limits (Defaults sind zu knapp!)
xdebug.var_display_max_depth = 10   ; Default 3
xdebug.var_display_max_children = 256 ; Default 128
xdebug.var_display_max_data = 1024  ; Default 512

Die drei Anzeige-Limits sind der Grund, warum var_dump oft ... statt der eigentlichen Daten zeigt.

4.3 Ohne Dateisystemzugriff

.htaccess (nur bei mod_php, nicht FPM; erfordert AllowOverride Options):

php_value xdebug.var_display_max_depth 10
php_flag xdebug.collect_return 1

Im Skript – funktioniert nur für PHP_INI_ALL-Direktiven:

ini_set('xdebug.var_display_max_depth', '10');
ini_set('xdebug.var_display_max_data', '1024');

Zwei Fallstricke:

  • Die Werte wirken erst auf var_dump-Aufrufe nach dem ini_set()
  • Gilt nur für den einen Request (nicht für separate Ajax-/Cron-Skripte)

xdebug.mode ist PHP_INI_SYSTEM und lässt sich zur Laufzeit nicht setzen.

Auf der CLI ganz ohne Datei:

php -d xdebug.mode=develop,trace -d xdebug.start_with_request=yes script.php

4.4 Prüfen, ob es angekommen ist

php -v                              # "with Xdebug v3.x.x"
php -i | grep xdebug.mode           # CLI

Im Browser: phpinfo() aufrufen, dort muss ein eigener Xdebug-Abschnitt erscheinen. Oder xdebug_info() – zeigt Konfiguration und aktuellen Zustand kompakt an.


5. Debuggen – wo sieht man was?

Drei Ebenen, unabhängig voneinander nutzbar.

5.1 Ebene 1: develop – ohne jede Zusatzsoftware

Aktiv allein durch xdebug.mode = develop:

  • var_dump() farbig, eingerückt, mit Datei und Zeile – kein <pre> mehr nötig
  • lesbare Stack-Traces bei jedem Fehler und jeder Warnung
  • xdebug_info() als Konfigurations-Übersicht im Browser

Ausgabeort: direkt im Browser bzw. auf STDOUT der CLI.

Nützliche Funktionen:

xdebug_break();                          // Breakpoint aus dem Code heraus
var_dump(xdebug_get_function_stack());   // Wer hat mich aufgerufen?
echo xdebug_call_line();
error_log(xdebug_print_function_stack()); // Stack ins Log

Zur Erinnerung – ohne Xdebug hilft <pre>, weil var_dump Plaintext ausgibt und der Browser Whitespace kollabiert:

function dbg(...$vars): void {
    ini_set('xdebug.var_display_max_depth', '10');
    ini_set('xdebug.var_display_max_data', '1024');
    echo '<pre style="text-align:left">';
    var_dump(...$vars);
    echo '</pre>';
}

var_dump() ist variadisch – mehrere Werte auf einmal sind erlaubt. Praktischer Trick zur Orientierung: var_dump(__LINE__, $row);

5.2 Ebene 2: trace – Step-Debugging im Nachhinein

Wenn keine IDE zur Verfügung steht, oft der stärkste Hebel.

xdebug.mode = develop,trace
xdebug.start_with_request = trigger
xdebug.output_dir = /tmp
xdebug.trace_format = 0
xdebug.collect_params = 4     ; volle Argumentwerte
xdebug.collect_return = 1

Ein Request mit ?XDEBUG_TRIGGER=1 schreibt /tmp/trace.*.xt – eine vollständige, eingerückte Aufrufliste mit allen Parametern und Rückgabewerten.

Ausgabeort: Textdatei in xdebug.output_dir, mit less / vim lesbar.

Gezielt im Code, ohne Trigger:

xdebug_start_trace('/tmp/meintrace');
// verdächtiger Abschnitt
xdebug_stop_trace();

Für die Frage „wer ruft das mit welchen Werten auf" meist schneller als ein Breakpoint.

5.3 Ebene 3: debug – Breakpoints

Grundprinzip: PHP verbindet sich zur IDE, nicht umgekehrt. Die IDE lauscht auf Port 9003, PHP klopft an, sobald ein Request getriggert wird. Der Listener muss also vor dem Request laufen – sonst passiert schlicht nichts.

Wichtig bei eingeschränkten Systemen: Der Client läuft auf der eigenen Maschine, nicht auf dem Server. Wenn dort nichts installiert werden darf, hilft ein SSH-Reverse-Tunnel:

ssh -R 9003:localhost:9003 user@server

Clients

Client Voraussetzung
VS Code Extension „PHP Debug" (xdebug.php-debug)
vdebug (Vim/Neovim) Vim mit +python3, spricht DBGp direkt – keine Node-Abhängigkeit
nvim-dap Node + Adapter aus vscode-php-debug
dbgp-tools reiner CLI-Client von Derick Rethans, via Composer

VS Code – .vscode/launch.json:

{
  "version": "0.2.0",
  "configurations": [
    { "name": "Listen for Xdebug", "type": "php", "request": "launch", "port": 9003 }
  ]
}

pathMappings nur bei Remote/Docker/Lima nötig.

vdebug – .vimrc:

let g:vdebug_options = {
\ 'port': 9003,
\ 'break_on_open': 0,
\ 'path_maps': {'/var/www/projekt': '/home/sven/projekt'},
\}

<F5> Listener starten · <F10> Breakpoint setzen · <F2> Step Over (path_maps: Serverpfad als Schlüssel)

nvim-dap:

dap.adapters.php = {
  type = 'executable',
  command = 'node',
  args = { os.getenv('HOME') .. '/.local/share/vscode-php-debug/out/phpDebug.js' }
}
dap.configurations.php = {
  { type = 'php', request = 'launch', name = 'Listen for Xdebug', port = 9003 }
}

Request triggern

Wegen start_with_request = trigger wartet nicht jeder Request auf den Debugger:

http://localhost/projekt/index.php?XDEBUG_TRIGGER=1
XDEBUG_TRIGGER=1 php script.php

Bequemer: Browser-Extension Xdebug Helper (Chrome/Firefox) – ein Klick setzt ein Cookie, danach ist jeder Request auf der Domain getriggert. Unerlässlich bei POST-Formularen und Ajax, wo kein Query-Parameter angehängt werden kann.

Typischer Ablauf

  1. Breakpoint in die interessierende Zeile setzen
  2. Listener in der IDE starten (VS Code: F5)
  3. Seite mit Trigger aufrufen → sie friert ein, die IDE springt nach vorn
  4. Variablen im Scope, Call-Stack, Watch-Ausdrücke auswerten ($row['datum'], count($items), sogar Methodenaufrufe)
  5. Step Over (F10) / Step Into (F11) / Continue (F5)

Was man sieht: Der Call-Stack ist meist der Aha-Moment – bei einem Fehler tief in einem Repository sofort sichtbar, welcher Controller mit welchen Parametern ihn ausgelöst hat.

Conditional Breakpoints: Rechtsklick auf den Breakpoint → Bedingung $id === 4711. Damit gezielt bei einem Datensatz in einer Schleife über 10.000 Zeilen anhalten.

5.4 Profiling

Bei Performance-Fragen temporär umschalten:

xdebug.mode = profile
xdebug.output_dir = /tmp

Schreibt cachegrind.out.*-Dateien, lesbar mit qcachegrind (brew install qcachegrind) – zeigt, welche Funktion wie viel Zeit frisst.

Nicht dauerhaft anlassen: bremst massiv und füllt /tmp.


6. Alternative & Ergänzung: Symfony VarDumper

Unabhängig von Xdebug, rein per Composer – nützlich auf Systemen, wo keine Extension installiert werden darf.

composer require --dev symfony/var-dumper
dump($row);        // weiter im Code
dd($row, $other);  // dump and die

Ausgabe im Browser aufklappbar, auf der CLI eingefärbt, kürzt rekursive Strukturen.

Dump-Server – fängt Ausgaben in einem separaten Terminal ab, statt sie in die Response zu schreiben. Praktisch bei API-Endpunkten, wo HTML im Output stört:

vendor/bin/var-dump-server
# dazu: VAR_DUMPER_FORMAT=server

Versionszuordnung (Composer löst das automatisch auf):

PHP symfony/var-dumper
7.1 ^4.4
7.2 – 7.4 ^5.4
8.0 – 8.1 ^6.4
8.2+ ^7

Absolutes Minimum, wenn gar nichts geht (Ajax, Redirects, Cronjobs):

error_log(var_export($row, true));
tail -f /var/log/apache2/error.log

7. Stolperfallen-Kurzliste

Symptom Ursache / Lösung
Extension wird ignoriert, keine Meldung extension= statt zend_extension=
incompatible architecture (have 'arm64', need 'x86_64') Build ohne Rosetta-Shell – arch -x86_64 /bin/zsh, dann neu bauen
already installed ... install failed pecl uninstall xdebug oder pecl install -f
Failed loading ... (no such file) beim pecl-Aufruf harmlos: PHP liest die INI, die .so wurde gerade gelöscht
pecl schreibt in falsche PHP-Installation XAMPP-eigenes pecl aus .../xamppfiles/bin/ verwenden
CLI zeigt Xdebug, Browser nicht Apache lädt eine andere php.ini → Pfad aus phpinfo() nehmen
var_dump zeigt ... statt Daten var_display_max_depth / _data / _children erhöhen
Breakpoint wird nie erreicht Listener nicht gestartet, oder Trigger fehlt
Port-Verbindung schlägt fehl Xdebug 2 nutzt 9000, Xdebug 3 nutzt 9003
autoconf: command not found brew install autoconf
Cannot find php-config falsches phpize/php-config – das aus dem Ziel-PHP nehmen

8. Randnotiz: var_dump mit Barewords

var_dump("Test", ffff);   // ohne $
PHP Verhalten
≤ 7.1 Notice: Use of undefined constant ffff → gibt string(4) "ffff" aus
7.2 – 7.4 Deprecated, sonst wie oben
8.0+ fataler Error: Undefined constant "ffff"