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

580 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `<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
```
Zur Erinnerung – ohne Xdebug hilft `<pre>`, 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 '<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.
```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'},
\}
```
`<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:
```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"` |