Habe durch Claude noch Änderungen vornehmen lassen

This commit is contained in:
Sven Riwoldt
2026-08-25 14:11:17 +02:00
parent 67121fb3f6
commit 3b095316d4
+319 -158
View File
@@ -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 `<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:
```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 `<pre>`, 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 `<?php phpinfo();` anlegen, `http://localhost/info.php`
aufrufen. Es muss ein eigener **Xdebug-Abschnitt** erscheinen. Danach Datei löschen.
Ab hier ist Xdebug für **alle** Skripte unter `htdocs/` aktiv ohne weitere Eintragung.
### Schritt 4 Erster Test ohne IDE
Beliebige Datei, z. B. `htdocs/test.php`:
```php
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>';
<?php
$daten = ['id' => 7, 'name' => 'Müller', 'rollen' => ['admin', 'user']];
var_dump($daten);
```
Aufrufen. Erwartetes Ergebnis: farbige, eingerückte Ausgabe mit Datei- und
Zeilenangabe **ohne** dass `<pre>` 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'},
\}
```
`<F5>` Listener starten · `<F10>` Breakpoint setzen · `<F2>` 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: `<pre>` nicht vergessen
`var_dump()` gibt Plaintext aus, der Browser kollabiert Whitespace. Deshalb:
```php
function dbg(...$vars): void {
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);`
---
## 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 |
| `<datum>` 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 $