diff --git a/xdebug-spickzettel.md b/xdebug-spickzettel.md new file mode 100644 index 0000000..2b7a6b0 --- /dev/null +++ b/xdebug-spickzettel.md @@ -0,0 +1,579 @@ +# 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 + +```bash +xcode-select --install # Compiler / Command Line Tools +brew install autoconf # phpize benötigt autoconf +``` + +Auf Apple Silicon zusätzlich: + +```bash +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: + +```bash +arch -x86_64 /bin/zsh +arch # muss "i386" ausgeben +``` + +### 1.3 Build via pecl + +```bash +# 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 + +```bash +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: + +```bash +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 + +```bash +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): + +```ini +[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 + +```bash +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) + +```bash +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 +``` + +```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. + +```bash +sudo systemctl restart apache2 +``` + +--- + +## 4. Wo wird was eingetragen? + +### 4.1 Die entscheidende Frage zuerst + +```bash +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 + +```ini +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`): + +```apache +php_value xdebug.var_display_max_depth 10 +php_flag xdebug.collect_return 1 +``` + +**Im Skript** – funktioniert nur für `PHP_INI_ALL`-Direktiven: + +```php +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:** + +```bash +php -d xdebug.mode=develop,trace -d xdebug.start_with_request=yes script.php +``` + +### 4.4 Prüfen, ob es angekommen ist + +```bash +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 `
` 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:
+
+```php
+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 `
`, weil `var_dump` Plaintext ausgibt und der
+Browser Whitespace kollabiert:
+
+```php
+function dbg(...$vars): void {
+    ini_set('xdebug.var_display_max_depth', '10');
+    ini_set('xdebug.var_display_max_data', '1024');
+    echo '
';
+    var_dump(...$vars);
+    echo '
'; +} +``` + +`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. + +```ini +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: + +```php +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: + +```bash +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`: + +```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`: + +```vim +let g:vdebug_options = { +\ 'port': 9003, +\ 'break_on_open': 0, +\ 'path_maps': {'/var/www/projekt': '/home/sven/projekt'}, +\} +``` + +`` Listener starten · `` Breakpoint setzen · `` Step Over +(`path_maps`: Serverpfad als Schlüssel) + +**nvim-dap**: + +```lua +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 +``` + +```bash +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: + +```ini +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. + +```bash +composer require --dev symfony/var-dumper +``` + +```php +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: + +```bash +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): + +```php +error_log(var_export($row, true)); +``` + +```bash +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 + +```php +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"` |