5 Die PHP-Laufzeitumgebung

Um PHP-Code effektiv zu entwickeln und zu optimieren, ist ein grundlegendes Verständnis der PHP-Laufzeitumgebung essentiell. Dieser Abschnitt erläutert, wie PHP-Code ausgeführt wird, wie der Lebenszyklus einer PHP-Anfrage aussieht und welche Kernmechanismen dabei eine Rolle spielen.

5.1 PHP als Interpreter

PHP ist eine interpretierte Sprache, was bedeutet, dass der Code zur Laufzeit in Maschinensprache übersetzt und ausgeführt wird – im Gegensatz zu kompilierten Sprachen wie C++, bei denen dieser Prozess vor der Ausführung stattfindet.

5.1.1 Ausführungsphasen

Die Ausführung eines PHP-Skripts durchläuft mehrere Phasen:

  1. Lexikalische Analyse (Lexing): Der PHP-Quellcode wird in Token zerlegt.
  2. Syntax-Analyse (Parsing): Die Token werden auf ihre syntaktische Korrektheit geprüft und ein abstrakter Syntaxbaum (AST) wird erstellt.
  3. Kompilierung: Der AST wird in Bytecode für die Zend Virtual Machine umgewandelt.
  4. Ausführung: Der Bytecode wird von der Zend Virtual Machine ausgeführt.
<?php
// Ein einfaches Beispiel, um den Ausführungsprozess zu verstehen
$a = 1;
$b = 2;
echo $a + $b;  // Diese Operation wird in Bytecode umgewandelt und dann ausgeführt
?>

5.2 Die Zend Engine

Das Herzstück der PHP-Laufzeitumgebung ist die Zend Engine, die für die Interpretation und Ausführung des PHP-Codes verantwortlich ist.

5.2.1 Komponenten der Zend Engine

  1. Lexer und Parser: Wandeln PHP-Code in einen abstrakten Syntaxbaum um
  2. Compiler: Erzeugt aus dem AST den Bytecode
  3. Zend Virtual Machine (ZVM): Führt den Bytecode aus
  4. Speicherverwaltung: Verwaltet die Speicherzuweisung und -freigabe

5.2.2 Evolution der Zend Engine

Version PHP-Version Hauptverbesserungen
Zend Engine 1 PHP 4 Erste Version mit virtuellem Maschinen-Konzept
Zend Engine 2 PHP 5 Verbessertes OOP-Modell, Exceptions
Zend Engine 3 PHP 7 Optimierte Speichernutzung, ~2x schneller
Zend Engine 4 PHP 8 JIT-Kompilierung, Referenzzählung, verbesserte Typinferenz

5.3 Ausführungsmodi

PHP kann in verschiedenen Modi ausgeführt werden, die unterschiedliche Einsatzzwecke und Leistungscharakteristiken haben:

5.3.1 CLI-Modus (Command Line Interface)

# Ausführung eines PHP-Skripts über die Kommandozeile
php script.php

# Ausführung eines einzelnen PHP-Befehls
php -r 'echo "Hallo Welt!";'

Der CLI-Modus ist für Kommandozeilen-Tools, Cron-Jobs und Skripte gedacht, die nicht über einen Webserver ausgeführt werden müssen.

5.3.2 Webserver-Modus

Im Webserver-Kontext gibt es verschiedene Methoden, PHP-Code auszuführen:

  1. Modul für den Webserver (z.B. mod_php für Apache): PHP wird direkt als Modul in den Webserver integriert, wodurch es effizient und einfach zu konfigurieren ist, aber weniger isoliert läuft.

  2. FastCGI-Prozess (PHP-FPM): PHP läuft als separater Prozess und kommuniziert über das FastCGI-Protokoll mit dem Webserver. Dies bietet bessere Isolation und Skalierbarkeit.

  3. CGI (Common Gateway Interface): Der älteste Modus, bei dem für jede Anfrage ein neuer PHP-Prozess gestartet wird. Ineffizient für hohe Last, aber mit der höchsten Isolation.

5.3.3 Eingebetteter Webserver für die Entwicklung

Seit PHP 5.4 gibt es einen eingebauten Webserver für die Entwicklung:

# Startet einen Webserver auf Port 8000 mit dem aktuellen Verzeichnis als Wurzel
php -S localhost:8000

Dieser Server ist nur für die Entwicklungsphase gedacht und sollte nicht in Produktionsumgebungen eingesetzt werden.

5.4 Der Anfrage-Lebenszyklus

Wenn eine Anfrage an ein PHP-Skript gesendet wird, durchläuft diese einen definierten Lebenszyklus:

  1. Initialisierung:
  2. Ausführung:
  3. Beendigung:

Hier ein beispielhafter Ablauf einer Webanfrage:

[Browser] --- HTTP-Anfrage ---> [Webserver] ---> [PHP-Interpreter] 
    |                                                  |
    |               PHP-Verarbeitung                   |
    |                  Datenbanken                     |
    |                  Dateisystem                     |
    |                                                  v
[Browser] <--- HTTP-Antwort --- [Webserver] <--- [PHP-Ausgabe]

5.5 OPcache: Performance-Boost durch Bytecode-Caching

Ein wichtiger Bestandteil moderner PHP-Umgebungen ist OPcache, der den kompilierten Bytecode zwischenspeichert, um wiederholte Kompilierungen zu vermeiden:

; Aktivierung von OPcache in php.ini
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.validate_timestamps=1
opcache.revalidate_freq=2

5.5.1 Ohne OPcache

Bei jedem Request muss PHP den Code neu lexen, parsen und kompilieren, bevor er ausgeführt werden kann - ein rechenintensiver Prozess.

5.5.2 Mit OPcache

Nach der ersten Ausführung wird der kompilierte Bytecode im Shared Memory gespeichert. Bei nachfolgenden Anfragen kann PHP direkt auf diesen Bytecode zugreifen und die Phasen Lexing, Parsing und Kompilierung überspringen.

Ohne OPcache: PHP-Code -> Lexing -> Parsing -> Kompilierung -> Ausführung
Mit OPcache: PHP-Code -> [Cache-Prüfung] -> Ausführung (wenn im Cache)

OPcache kann die Performance um das 2- bis 5-fache verbessern, abhängig von der Komplexität des Codes.

5.6 JIT-Compiler in PHP 8

Mit PHP 8 wurde ein Just-In-Time (JIT) Compiler eingeführt, der den PHP-Bytecode zur Laufzeit in nativen Maschinencode umwandeln kann. Dies führt zu signifikanten Performance-Verbesserungen bei CPU-intensiven Aufgaben:

; Aktivierung von JIT in php.ini
opcache.enable=1
opcache.jit_buffer_size=100M
opcache.jit=1255

5.6.1 JIT-Modi

Der JIT-Compiler in PHP 8 bietet verschiedene Modi, die über die Einstellung opcache.jit konfiguriert werden können:

5.6.2 Wann lohnt sich JIT?

JIT-Kompilierung ist besonders vorteilhaft für: - Mathematische Berechnungen - Bildverarbeitung - Algorithmen mit vielen Schleifen und Berechnungen - CPU-intensive Hintergrundprozesse

Bei typischen Web-Anwendungen, die durch I/O-Operationen (Datenbank, Dateisystem) begrenzt sind, kann der Vorteil geringer ausfallen.

5.7 Garbage Collection

PHP verwendet ein automatisches Speichermanagement mit Referenzzählung und einem zyklischen Garbage Collector:

5.7.1 Referenzzählung

Jeder Variable wird ein Referenzzähler zugewiesen. Wenn der Zähler auf 0 fällt, wird der Speicher freigegeben:

<?php
$a = "Hallo"; // Referenzzähler für den String "Hallo" ist 1
$b = $a;      // Referenzzähler erhöht sich auf 2
unset($a);    // Referenzzähler sinkt auf 1
unset($b);    // Referenzzähler sinkt auf 0, Speicher wird freigegeben
?>

5.7.2 Zyklischer Garbage Collector

Bei zirkulären Referenzen kann die Referenzzählung allein nicht erkennen, wann Speicher freigegeben werden kann:

<?php
$a = new stdClass();
$b = new stdClass();
$a->ref = $b;    // $a referenziert $b
$b->ref = $a;    // $b referenziert $a
unset($a);       // Referenzzähler für $a ist noch nicht 0 (wegen $b->ref)
unset($b);       // Referenzzähler für $b ist noch nicht 0 (wegen der Referenz in $a)

// Hier würde ohne zyklischen Garbage Collector ein Speicherleck entstehen
?>

Der zyklische Garbage Collector identifiziert solche “Referenzinseln” und gibt sie frei, wenn sie nicht mehr erreichbar sind.

5.8 Superglobals und vordefinierte Variablen

PHP stellt verschiedene vordefinierte Variablen bereit, die im gesamten Skript verfügbar sind:

<?php
// Server- und Anfrageinformationen
echo $_SERVER['HTTP_USER_AGENT'];  // Browser-Informationen

// URL-Parameter
$id = $_GET['id'];                // Parameter aus der URL-Abfrage

// Formulardaten
$username = $_POST['username'];   // Über POST übermittelte Formulardaten

// Cookies
$lastVisit = $_COOKIE['last_visit']; // Gespeicherte Cookies

// Session-Variablen
$_SESSION['user_id'] = 123;       // In der Session gespeicherte Daten

// Umgebungsvariablen
$path = $_ENV['PATH'];            // Umgebungsvariablen des Servers

// Dateien
$uploadedFile = $_FILES['userfile']; // Hochgeladene Dateien
?>

5.9 Konfiguration der Laufzeitumgebung

Die PHP-Laufzeitumgebung kann über verschiedene Methoden konfiguriert werden:

5.9.1 Über php.ini

Die zentrale Konfigurationsdatei für PHP:

; Beispiel für php.ini-Einstellungen
max_execution_time = 30
memory_limit = 128M
error_reporting = E_ALL & ~E_DEPRECATED
display_errors = Off

5.9.2 Über .htaccess (Apache)

Für Apache-Webserver kann PHP auch über .htaccess-Dateien konfiguriert werden:

# PHP-Einstellungen in .htaccess
php_value memory_limit 256M
php_flag display_errors On

5.9.3 Zur Laufzeit im Skript

Viele Einstellungen können auch direkt im PHP-Code geändert werden:

<?php
// PHP-Einstellungen im Code ändern
ini_set('display_errors', '1');
ini_set('memory_limit', '512M');

// Aktuelle Einstellung abrufen
echo ini_get('max_execution_time');
?>

5.9.4 Priorität der Konfigurationsquellen

Die Einstellungen werden in dieser Reihenfolge geladen (wobei spätere Quellen frühere überschreiben können):

  1. php.ini (Master-Konfiguration)
  2. Apache-Konfigurationsdateien (httpd.conf, .htaccess)
  3. Webserver-Konfiguration (für FastCGI, PHP-FPM)
  4. Skript-Level-Einstellungen (ini_set())

5.10 Erweiterungen und ihre Auswirkungen auf die Laufzeitumgebung

PHP-Erweiterungen fügen der Laufzeitumgebung zusätzliche Funktionalitäten hinzu:

<?php
// Beispiel für die Verwendung von Erweiterungen
if (extension_loaded('gd')) {
    // GD-Bibliothek für Bildbearbeitung verwenden
    $image = imagecreatefrompng('input.png');
    // ...
}

if (extension_loaded('pdo_mysql')) {
    // PDO für MySQL-Datenbankverbindungen verwenden
    $pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
    // ...
}
?>

5.10.1 Überprüfen und Laden von Erweiterungen

<?php
// Überprüfen, ob eine Erweiterung geladen ist
if (!extension_loaded('mysqli')) {
    die('MySQLi-Erweiterung ist nicht verfügbar!');
}

// Alle geladenen Erweiterungen anzeigen
print_r(get_loaded_extensions());

// Dynamisches Laden einer Erweiterung (wenn vom Server erlaubt)
if (function_exists('dl')) {
    dl('extension_name.so'); // Linux/Unix
    // oder
    dl('extension_name.dll'); // Windows
}
?>

5.11 Thread-Safety und PHP

PHP bietet zwei verschiedene Builds:

  1. Thread-Safe (TS): Geeignet für Multi-Threaded-Webserver wie Apache mit MPM-Worker oder Multi-Threaded IIS.
  2. Non-Thread-Safe (NTS): Optimiert für Single-Threaded-Webserver oder FastCGI-Modus.
<?php
// Überprüfen, ob PHP thread-safe kompiliert wurde
echo PHP_ZTS ? 'Thread-Safe' : 'Non-Thread-Safe';
?>

Für die meisten modernen Setups (PHP-FPM, FastCGI) ist die NTS-Version empfehlenswert, da sie eine bessere Performance bietet und keine Thread-Sicherheit benötigt.

5.12 PHP in verschiedenen Umgebungen

Die PHP-Laufzeitumgebung verhält sich je nach Kontext unterschiedlich:

5.12.1 CLI-Spezifisches Verhalten

<?php
// Überprüfen, ob das Skript im CLI-Modus läuft
if (php_sapi_name() === 'cli') {
    echo "Dieses Skript läuft auf der Kommandozeile\n";
    // CLI-spezifische Aktionen
    echo "Argumente: " . implode(', ', array_slice($argv, 1)) . "\n";
}
?>

5.12.2 Webspezifisches Verhalten

<?php
// Webspezifische Aktionen
if (php_sapi_name() !== 'cli') {
    header('Content-Type: text/html; charset=utf-8');
    session_start();
    // ...
}
?>