Debugging PHP Code in PhpStorm (mit Xdebug)
PHP kann in verschiedenen Kontexten ausgeführt werden. Beispielsweise als Kommandozeilenapplikation (CLI) oder als Apache Modul. Je nach Kontext sind andere Schritte zu setzen falls man den ausgeführten PHP-Code debuggen möchte.
In diesem Blog Post betrachten wir das Szenario einer PHP-Applikation die eine REST-Schnittstelle bereistellt. Der PHP Code wird von PHP-FPM in einem Docker Container ausgeführt.
Ursprünglich handelte es sich bei PHP-FPM um ein eigenständiges Projetk. Seit PHP Version 5.3.3 ist es aber offizieller Bestandteil des PHP-Projekts. Die Webseite des ursprünglichen PHP-FPM Projekts is übrigens noch hier zu finden.
Beginnen wir mit unserer Docker Umgebung. Speichere die folgenden Dateien in ein beliebiges Arbeitsverzeichnis.
docker-compose
Datei /tmp/src/docker-compose.yml
:
version: '3.3'
services:
nginx:
build: ./nginx
ports:
- 80:80
fpm:
build: ./fpm
nginx Docker Container
Datei /tmp/src/nginx/Dockerfile
:
FROM nginx:1.21.5-alpine
COPY dockerfiles/etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf
Datei /tmp/src/nginx/dockerfiles/etc/nginx/conf.d/default.conf
:
server {
listen 80;
server_name localhost;
root /var/www/html;
location ~ \.php$ {
fastcgi_pass fpm:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
fpm (PHP) Container
Datei /tmp/src/fpm/Dockerfile
:
FROM php:8.1.1-fpm-alpine3.15
COPY dockerfiles/var/www/html/api.php /var/www/html/api.php
############################################################
# install xdebug begin
RUN apk add --no-cache --virtual .build-deps $PHPIZE_DEPS \
&& pecl install xdebug \
&& docker-php-ext-enable xdebug \
&& apk del -f .build-deps \
# Prepare log file for xdebug.
&& touch /tmp/xdebug.log && chmod 0666 /tmp/xdebug.log
RUN echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/conf.d/xdebug.ini \
&& echo "xdebug.mode=debug" >> /usr/local/etc/php/conf.d/xdebug.ini \
&& echo "xdebug.log=/tmp/xdebug.log" >> /usr/local/etc/php/conf.d/xdebug.ini \
&& echo "xdebug.discover_client_host=1" >> /usr/local/etc/php/conf.d/xdebug.ini \
&& echo "xdebug.client_port=9000" >> /usr/local/etc/php/conf.d/xdebug.ini
# install xdebug end
############################################################
Zu guter letzt implementieren wir noch eine einfache
HTTP REST-API in PHP. Datei /tmp/src/fpm/dockerfiles/var/www/html/api.php
:
<?php
// Just a simple REST-like API implmemented in plain PHP
if (!empty($_GET['name'])) {
$name = $_GET['name'];
$price = get_price($name);
if (empty($price)) {
response(401, NULL);
} else {
response(200, $price);
}
} else {
response(400, "Invalid Request", NULL);
}
function response($status, $data)
{
header("Content-Type:application/json");
header("HTTP/1.1 " . $status);
$json_response = json_encode($data);
echo $json_response;
}
function get_price($name)
{
$products = [
"book" => 200,
"pen" => 100,
"pencil" => 50
];
foreach ($products as $product => $price) {
if ($product == $name) {
return $price;
break;
}
}
}
Eure Dateistruktur sollte nun aussehen wie folgt:
/tmp/src/fpm/dockerfiles/var/www/html/api.php
/tmp/src/fpm/Dockerfile
/tmp/src/nginx/dockerfiles/etc/nginx/conf.d/default.conf
/tmp/src/nginx/Dockerfile
/tmp/src/docker-compose.yml
Wechsle nun ins Arbeitsverzeichnis /tmp/src
und starte die Docker
Container mit folgendem Kommando:
user@pc:/tmp/src$ docker-compose up --build
Mit dem Programm https://curl.se/ kann man nun die REST-API testen.
Falls man https://curl.se/ nicht installiert hat kann man statt
dessen auch Insomnia oder
Postman verwenden. Aus eigener Erfahrung
kann ich euch aber die Verwendung von curl
empfehlen. Erst wenn es darum
geht sich eine Template-Bibliothek mit Dutzenden REST-Requests
anzulegen und zu organisieren haben grafische Programme
wie Insomnia oder
Postman handfeste Vorteile gegenüber einer
reinen CLI-Applikation. Wenn es aber um das Debugging oder
Troubleshooting eines konkrete Requests geht greife ich aber so gut wie
immer zu https://curl.se/, da es oft wichtige Details des
HTTP-Protokolls (HTTP Reply Header, …) nicht irgendwo in einem Untermenü einer
grafischen Benutzeroberfläche versteckt. Nun aber zum eigentlichen Test der
REST-API. Dazu öffnet man eine neue Shell und führt folgendes Kommando aus:
user@pc:/tmp/src$ curl -v 'http://localhost/api.php?name=book'
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /api.php?name=book HTTP/1.1
> Host: localhost
> User-Agent: curl/7.74.0
> Accept: */*
< HTTP/1.1 200 OK
< Server: nginx/1.21.5
< Date: Tue, 18 Jan 2022 14:34:28 GMT
< Content-Type: application/json
< Transfer-Encoding: chunked
< Connection: keep-alive
< X-Powered-By: PHP/8.1.1
200
user@pc:/tmp/src$
Wenn alles klappt schickt die RES-API den Wert 200
zurück. Soweit so gut. Nun wollen wir den PHP-Quellcode in PhpStorm
debuggen. Damit das klappt haben wir im fpm
Docker Container bereits xdebug
installiert und konfiguriert. xdebug ist eines der beiden Programme
die man aktuell zum Debuggen von PHP verwenden kann. Das andere Programm ist
der Zend Debugger.
Öffne den Pfad /tmp/src/fpm/dockerfiles/var/www/html
in PhpStorm.
Klicke auf das Symbol “Start Listening for PHP Debug Connections”.
Du findest dieses Symbol in der rechten oberen Ecke des PhpStorm
Fensters:
Danach platziere einen Breakpoint an einer Stelle des Programmcodes der sicher ausgeführt wird:
Falls du nicht sicher bist wo du einen Breakpoint im Quellcode setzen musst um einen Request an die REST-API zu debuggen kannst du folgende Option aktivieren:
File
->Settings
->PHP
->Debug
- Im Abschnitt “External connections” aktiviere die Option “Break at first line in PHP scripts”.
Zu guter letzt müssen wir noch ein “Path Mapping” definieren.
Navigiere dazu zum entsprechenden Menüpunkt in den Settings
(File
-> Settings
-> PHP
-> Servers
) und fülle die
Details aus wie im Screenshot ersichtlich:
Dieses Path Mapping kann theoretisch beim ersten Request auch automatisch von PhpStorm erstellt werden. In diesem Fall öffnet PhpStorm automatisch das Fenster “Incoming Connection From Xdebug”. Dort gäbe es auch die Möglichkeit ein Path Mapping eines Deployments zu verwenden (“Import mappings from deployment”). Diese Option scheint aktuell aber nicht zu funktionieren. Es gibt diesbezüglich zumindest einen offenen Bug.
Sobald wir nun einen weitere Request absetzen wird der Debugger in PhpStorm aktiv und man kann Schritt für Schritt den Programmcode ausführen und den Inhalt von Variablen inspizieren: