diff --git a/xdebug-spickzettel.md b/xdebug-spickzettel.md index 2b7a6b0..933a143 100644 --- a/xdebug-spickzettel.md +++ b/xdebug-spickzettel.md @@ -4,7 +4,34 @@ Stand: Xdebug 3.x (Modus-Konfiguration ab Xdebug 3, Port **9003**) --- -## 0. Vorab: Versionsmatrix +## Die wichtigste Antwort vorweg + +**Nein – man trägt nichts in die einzelnen PHP-Dateien ein.** + +Xdebug wird **einmal** in der `php.ini` konfiguriert. Danach ist es für *jedes* PHP-Skript +aktiv, das über diesen Webserver bzw. diese CLI läuft. Kein `require`, kein `include`, +keine Zeile im Quellcode. + +``` +php.ini ändern → Apache neu starten → gilt ab sofort überall +``` + +Das `ini_set(...)` aus dem Gesprächsverlauf war ausdrücklich der **Notfallweg** für +Systeme, auf denen man keine INI-Datei anfassen darf. Wer Zugriff auf die `php.ini` hat, +braucht es nicht. + +Im Code gibt es nur zwei *optionale* Dinge, die man punktuell einsetzen kann: + +```php +xdebug_break(); // setzt einen Breakpoint ohne IDE-Klick +xdebug_start_trace('/tmp/xy'); // startet ein Aufrufprotokoll ab hier +``` + +Beides ist Zusatzkomfort, keine Voraussetzung. + +--- + +## 0. Versionsmatrix | PHP | Xdebug | Konfigurationsstil | |---|---|---| @@ -17,7 +44,7 @@ Stand: Xdebug 3.x (Modus-Konfiguration ab Xdebug 3, Port **9003**) > `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. +`20220829` = PHP 8.2 · `20230831` = PHP 8.3 · `20240924` = PHP 8.4 --- @@ -99,13 +126,9 @@ sudo cp modules/xdebug.so \ /Applications/XAMPP/xamppfiles/lib/php/extensions/no-debug-non-zts-20220829/ ``` -### 1.6 XAMPP-VM (macOS) +### 1.6 Alternative ohne Compile-Schmerz -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 +Wer nicht an XAMPP gebunden ist: ```bash brew install php # natives arm64-PHP @@ -162,14 +185,6 @@ 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 @@ -250,8 +265,6 @@ php -i | grep -E "Loaded Configuration|Scan this dir" **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-…`** @@ -286,7 +299,7 @@ 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 +### 4.3 Nur wenn keine INI-Datei erreichbar ist **`.htaccess`** (nur bei mod_php, nicht FPM; erfordert `AllowOverride Options`): @@ -302,6 +315,17 @@ ini_set('xdebug.var_display_max_depth', '10'); ini_set('xdebug.var_display_max_data', '1024'); ``` +Sinnvoll dann **einmal zentral** im Bootstrap, nicht in jeder Datei: + +```php +require __DIR__ . '/vendor/autoload.php'; + +if (($_ENV['APP_ENV'] ?? 'prod') === 'dev') { + 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) @@ -314,60 +338,249 @@ Zwei Fallstricke: 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? +## 5. Schritt für Schritt: Debuggen unter XAMPP (macOS/Windows) -Drei Ebenen, unabhängig voneinander nutzbar. +Ausgangslage: Xdebug ist installiert, `php -v` zeigt „with Xdebug". -### 5.1 Ebene 1: `develop` – ohne jede Zusatzsoftware +### Schritt 1 – Konfiguration eintragen (einmalig) -Aktiv allein durch `xdebug.mode = develop`: +`php.ini` öffnen. Pfad zur *richtigen* Datei ermitteln: -- `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 +```bash +# macOS +/Applications/XAMPP/xamppfiles/bin/php -i | grep "Loaded Configuration" +``` +``` +:: Windows +C:\xampp\php\php.exe -i | findstr "Loaded Configuration" ``` -Zur Erinnerung – ohne Xdebug hilft ``, weil `var_dump` Plaintext ausgibt und der -Browser Whitespace kollabiert: +Am Ende der Datei eintragen (jede Direktive auf einer **eigenen Zeile**): + +```ini +[XDebug] +zend_extension = /Applications/XAMPP/xamppfiles/lib/php/extensions/no-debug-non-zts-20220829/xdebug.so +xdebug.mode = develop,debug +xdebug.start_with_request = trigger +xdebug.client_port = 9003 +xdebug.client_host = 127.0.0.1 +xdebug.var_display_max_depth = 10 +xdebug.var_display_max_data = 1024 +xdebug.var_display_max_children = 256 +``` + +Unter Windows genügt `zend_extension = xdebug`, weil die DLL in `ext\` liegt. + +### Schritt 2 – Apache neu starten + +```bash +sudo /Applications/XAMPP/xamppfiles/xampp restart +``` + +Windows: XAMPP Control Panel → Apache **Stop**, dann **Start**. + +### Schritt 3 – Kontrollieren + +Datei `htdocs/info.php` mit `'; - var_dump(...$vars); - echo ''; + 7, 'name' => 'Müller', 'rollen' => ['admin', 'user']]; +var_dump($daten); +``` + +Aufrufen. Erwartetes Ergebnis: farbige, eingerückte Ausgabe mit Datei- und +Zeilenangabe – **ohne** dass `` nötig wäre. Das ist der `develop`-Modus. + +Ebenso: einen absichtlichen Fehler einbauen (`$x->foo();`) – statt der nackten +PHP-Meldung kommt ein tabellarischer Stack-Trace mit allen Aufrufen und Parametern. + +### Schritt 5 – Breakpoint-Debugging vorbereiten + +**Grundprinzip verstehen:** PHP verbindet sich **zur IDE**, nicht umgekehrt. Die IDE +lauscht auf Port 9003, PHP klopft an. Der Listener muss also **vor** dem Seitenaufruf +laufen – sonst passiert schlicht nichts. + +VS Code: Extension „PHP Debug" (xdebug.php-debug) installieren, dann im Projektordner +`.vscode/launch.json` anlegen: + +```json +{ + "version": "0.2.0", + "configurations": [ + { "name": "Listen for Xdebug", "type": "php", "request": "launch", "port": 9003 } + ] } ``` -`var_dump()` ist variadisch – mehrere Werte auf einmal sind erlaubt. Praktischer Trick -zur Orientierung: `var_dump(__LINE__, $row);` +`pathMappings` ist **nicht** nötig, weil Code und IDE auf derselben Maschine liegen. -### 5.2 Ebene 2: `trace` – Step-Debugging im Nachhinein +### Schritt 6 – Breakpoint setzen und Session starten -Wenn keine IDE zur Verfügung steht, oft der stärkste Hebel. +1. In `test.php` links neben die Zeilennummer klicken → roter Punkt erscheint +2. **F5** drücken → unten wird die Statusleiste orange, VS Code lauscht jetzt +3. Im Browser aufrufen: `http://localhost/test.php?XDEBUG_TRIGGER=1` +4. Die Seite bleibt hängen, VS Code springt in den Vordergrund und markiert die Zeile + +### Schritt 7 – Was man jetzt sieht + +| Bereich in VS Code | Inhalt | +|---|---| +| **Variables** (links oben) | alle Variablen im aktuellen Scope, aufklappbar | +| **Watch** | eigene Ausdrücke: `$daten['name']`, `count($items)`, Methodenaufrufe | +| **Call Stack** | wer diese Funktion aufgerufen hat, mit Parametern – Klick springt dorthin | +| **Breakpoints** | Liste aller gesetzten Haltepunkte | +| **Debug Console** | beliebigen PHP-Code im aktuellen Kontext ausführen | + +Steuerung: **F10** Step Over (nächste Zeile) · **F11** Step Into (in Funktion hinein) · +**Shift+F11** Step Out · **F5** Continue (bis zum nächsten Breakpoint) + +### Schritt 8 – Trigger bequemer machen + +Der Query-Parameter `?XDEBUG_TRIGGER=1` funktioniert nicht bei POST-Formularen oder +Ajax. Lösung: Browser-Extension **Xdebug Helper** (Chrome/Firefox) – ein Klick auf den +Käfer setzt ein Cookie, danach ist jeder Request auf der Domain getriggert. + +CLI-Äquivalent: + +```bash +XDEBUG_TRIGGER=1 /Applications/XAMPP/xamppfiles/bin/php script.php +``` + +### Schritt 9 – Session beenden + +**Shift+F5** in VS Code stoppt den Listener. Die Seite im Browser läuft dann normal +weiter. Ohne laufenden Listener werden Breakpoints einfach ignoriert – es entsteht kein +Fehler. + +--- + +## 6. Schritt für Schritt: Debuggen unter Debian + +Zwei Fälle: Code liegt lokal auf demselben Rechner, oder auf einem entfernten Server. + +### Schritt 1 – Installieren und konfigurieren + +```bash +sudo apt install php-xdebug +sudo nano /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 +``` + +```bash +sudo systemctl restart apache2 +``` + +Für CLI-Skripte dieselbe Datei zusätzlich unter `/etc/php/8.4/cli/conf.d/` ablegen – +oder einmal in `mods-available/` und mit `phpenmod xdebug-local` für beide aktivieren. + +### Schritt 2 – Kontrollieren + +```bash +php -v # CLI: "with Xdebug v3.x.x" +php -i | grep xdebug.mode +``` + +Für den Webserver: `phpinfo()` im Browser aufrufen – nur das zeigt, was Apache lädt. +Danach die Datei löschen. + +### Schritt 3 – Client-Position klären + +Der Debug-Client läuft auf **deiner** Maschine, nicht auf dem Server. + +| Situation | Vorgehen | +|---|---| +| Code liegt lokal | nichts weiter nötig, Listener einfach starten | +| Code auf entferntem Server | SSH-Reverse-Tunnel | + +```bash +ssh -R 9003:localhost:9003 user@server +``` + +Damit verbindet sich Xdebug auf dem Server nach `localhost:9003` – und landet über den +Tunnel bei dir. Der Tunnel muss offen bleiben, solange du debuggst. + +### Schritt 4 – Pfade zuordnen (nur bei Remote) + +Die IDE muss wissen, welche lokale Datei welchem Serverpfad entspricht. + +VS Code, `.vscode/launch.json`: + +```json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Listen for Xdebug", + "type": "php", + "request": "launch", + "port": 9003, + "pathMappings": { + "/var/www/projekt": "${workspaceFolder}" + } + } + ] +} +``` + +Links steht immer der **Serverpfad**, rechts der lokale. Stimmt das nicht, hält der +Debugger zwar an, öffnet aber keine Datei – der häufigste Frustpunkt bei Remote-Setups. + +### Schritt 5 – Ohne VS Code: vdebug in Vim/Neovim + +Spricht DBGp direkt, braucht nur Vim mit `+python3` – keine Node-Abhängigkeit. + +`.vimrc`: + +```vim +let g:vdebug_options = { +\ 'port': 9003, +\ 'break_on_open': 0, +\ 'path_maps': {'/var/www/projekt': '/home/sven/projekt'}, +\} +``` + +Bedienung: **F5** Listener starten · **F10** Breakpoint setzen/entfernen · +**F2** Step Over · **F3** Step Into · **F4** Step Out · **F6** beenden + +Sobald die Session steht, teilt sich das Fenster: Quellcode, Variablen im aktuellen +Scope, Call-Stack und ein Watch-Bereich, in dem sich beliebige Ausdrücke auswerten +lassen. + +Alternativen: **nvim-dap** (moderneres UI, braucht Node + `vscode-php-debug`) und +**dbgp-tools** von Derick Rethans (reiner CLI-Client, via Composer). + +### Schritt 6 – Request triggern + +```bash +# Webseite +http://server/projekt/index.php?XDEBUG_TRIGGER=1 + +# CLI-Skript auf dem Server +XDEBUG_TRIGGER=1 php artisan irgendwas +``` + +### Schritt 7 – Wenn keine IDE möglich ist: Trace-Dateien + +Auf gesperrten Systemen oft der stärkste Hebel – ein Step-Debugger im Nachhinein. ```ini xdebug.mode = develop,trace @@ -379,11 +592,13 @@ 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. +eingerückte Aufrufliste mit allen Parametern und Rückgabewerten: -**Ausgabeort:** Textdatei in `xdebug.output_dir`, mit `less` / `vim` lesbar. +```bash +ls -t /tmp/trace.*.xt | head -1 | xargs less +``` -Gezielt im Code, ohne Trigger: +Gezielt nur einen Abschnitt protokollieren: ```php xdebug_start_trace('/tmp/meintrace'); @@ -393,117 +608,47 @@ 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. +## 7. Was man wo sieht – Überblick -**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: +| Modus | Ausgabeort | Zeigt | +|---|---|---| +| `develop` | Browser / STDOUT | farbiges `var_dump`, Stack-Traces bei Fehlern | +| `trace` | Datei in `xdebug.output_dir` | kompletter Aufrufbaum mit Parametern | +| `debug` | IDE / Vim | Variablen, Call-Stack, Watch, schrittweise Ausführung | +| `profile` | `cachegrind.out.*` in `output_dir` | Laufzeit pro Funktion (qcachegrind) | -```bash -ssh -R 9003:localhost:9003 user@server +Zusätzlich im Code nutzbar (alle Modi): + +```php +xdebug_info(); // Konfigurations-Übersicht im Browser +xdebug_break(); // Breakpoint aus dem Code heraus +var_dump(xdebug_get_function_stack()); // Wer hat mich aufgerufen? +error_log(xdebug_print_function_stack()); // Stack ins Logfile +echo xdebug_call_line(); ``` -#### Clients +### Conditional Breakpoints -| 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 | +Rechtsklick auf den Breakpoint → Bedingung eintragen, z. B. `$id === 4711`. Damit hält +man gezielt bei einem Datensatz in einer Schleife über 10.000 Zeilen an. Einer der +größten praktischen Vorteile gegenüber `var_dump`. -**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: +### Profiling ```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. +Auswertung mit **qcachegrind** (`brew install qcachegrind`, `apt install kcachegrind`). > Nicht dauerhaft anlassen: bremst massiv und füllt `/tmp`. --- -## 6. Alternative & Ergänzung: Symfony VarDumper +## 8. Alternative & Ergänzung: Symfony VarDumper Unabhängig von Xdebug, rein per Composer – nützlich auf Systemen, wo keine Extension installiert werden darf. @@ -527,8 +672,6 @@ 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` | @@ -546,9 +689,24 @@ error_log(var_export($row, true)); tail -f /var/log/apache2/error.log ``` +### Ohne Xdebug: ` ` nicht vergessen + +`var_dump()` gibt Plaintext aus, der Browser kollabiert Whitespace. Deshalb: + +```php +function dbg(...$vars): void { + echo ''; + var_dump(...$vars); + echo ''; +} +``` + +`var_dump()` ist variadisch – mehrere Werte auf einmal sind erlaubt. Praktischer Trick +zur Orientierung: `var_dump(__LINE__, $row);` + --- -## 7. Stolperfallen-Kurzliste +## 9. Stolperfallen-Kurzliste | Symptom | Ursache / Lösung | |---|---| @@ -558,15 +716,18 @@ tail -f /var/log/apache2/error.log | `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 | +| Direktiven wirken nicht | alles in einer Zeile statt untereinander | +| `` im Pfad | Platzhalter! echten Namen per `php -i \| grep extension_dir` ermitteln | | `var_dump` zeigt `...` statt Daten | `var_display_max_depth` / `_data` / `_children` erhöhen | | Breakpoint wird nie erreicht | Listener nicht gestartet, oder Trigger fehlt | +| Debugger hält an, öffnet aber keine Datei | `pathMappings` / `path_maps` falsch (Serverpfad links) | | 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 +## 10. Randnotiz: `var_dump` mit Barewords ```php var_dump("Test", ffff); // ohne $