PHP 7 auf 8: Wo es bricht und wie Sie es vorher wissen
Die meisten Fehler beim PHP-Sprung entstehen nicht im Framework, sondern im eigenen Code und in alten Abhängigkeiten.
Der Sprung von PHP 7 auf 8 ist kein einzelnes Update, sondern mehrere. Zwischen PHP 7.4 und 8.3 liegen vier Minor-Versionen mit eigenen Änderungen an Typen, Fehlerverhalten und Erweiterungen. Die meisten Anwendungen brechen nicht beim Start. Sie brechen an Stellen, die jahrelang unbemerkt funktioniert haben.
Dieser Text zeigt, wo die typischen Bruchstellen liegen und wie Sie sie vor dem Update finden.
Typen werden strenger
PHP 8 prüft Typen genauer, auch bei internen Funktionen. Wer null an einen
Parameter übergibt, der keinen null-Wert erlaubt, erhält ab PHP 8.1 eine
Deprecation-Warnung. Beispiele sind strpos(), strlen(), trim() oder
htmlspecialchars() mit null als Argument. In PHP 7 wurde null still zu
einer leeren Zeichenkette umgewandelt.
Interne Funktionen werfen außerdem häufiger TypeError, statt null oder
false zurückzugeben. Code, der auf einen Rückgabewert prüft, läuft ins Leere.
Dieselbe Änderung trifft Union-Types und Rückgabetypen in eigenen Signaturen.
Was PHP 7 toleriert hat, wird zur Laufzeit abgelehnt.
Ein verwandter Punkt betrifft Standardwerte. Signaturen wie
function f(string $x = null) waren lange üblich. Wer direkt auf PHP 8.4 oder
später geht, findet sie als veraltet markiert. Prüfen Sie solche Stellen, wenn
Sie die Signaturen ohnehin anfassen.
Warnungen, die zu Fehlern werden
Ein nicht vorhandener Array-Schlüssel erzeugte in PHP 7 einen Notice-Hinweis.
In PHP 8.0 ist daraus eine Warnung geworden: "Undefined array key". Der Zugriff
auf eine Eigenschaft eines null-Werts erzeugt eine ähnliche Warnung.
Warnungen sind für sich genommen nicht fatal. Viele Frameworks und Fehlerbehandler wandeln sie jedoch in Exceptions um, etwa der Symfony ErrorHandler im Debug-Modus. Damit wird aus einer jahrelang ignorierten Warnung ein Abbruch. In Produktion bleibt der sichtbare Effekt oft Lograuschen, das echte Fehler überdeckt.
Prüfen Sie deshalb, wie Ihre Anwendung mit E_WARNING und E_DEPRECATED
umgeht. Wenn ein Error-Handler daraus Exceptions macht, gehört das vor das
Update.
Zeichenketten und Zahlen
PHP 8 hat die Vergleiche zwischen Zeichenketten und Zahlen geändert.
0 == "foo" ergab in PHP 7 true, weil die nicht-numerische Zeichenkette zu
0 konvertiert wurde. In PHP 8 wird stattdessen die Zahl in eine Zeichenkette
umgewandelt, das Ergebnis ist false. 0 == "" ist ebenfalls nicht mehr wahr.
Der Vergleich "0" == "foo" war schon in PHP 7 falsch und bleibt falsch, weil
zwei Zeichenketten in beiden Versionen als Zeichenketten verglichen werden.
Solche Vergleiche tauchen in gewachsenem Code häufig auf: Statusprüfungen,
Rabattlogiken, Gutscheincodes, Filter aus Formularen. Suchen Sie nach lockeren
Vergleichen mit == in Pfaden, in denen Geld oder Berechtigungen eine Rolle
spielen, und ersetzen Sie sie durch ===. Dafür braucht es kein spezielles
Werkzeug. Eine gezielte Suche reicht als Anfang.
PHP 8 liefert außerdem str_contains(), str_starts_with() und
str_ends_with(). Sie ersetzen viele strpos()-Vergleiche und sind leichter zu
lesen. Beim Umbau der kritischen Pfade lassen sich beide Arbeiten verbinden.
Fehler und Exceptions
PHP 8 wirft für ungültige Argumente häufiger ValueError und TypeError. Ein
negativer Wert in str_repeat(), ein ungültiger Modus in fopen() oder ein
fehlerhafter Aufruf von DateTime führen jetzt zu einer Exception statt zu
einem stillen Fehlschlag.
Wichtig ist die Vererbung: TypeError und ValueError erben von Error, nicht
von Exception. Ein catch (Exception $e) fängt sie nicht ab. Prüfen Sie, ob
Ihre zentralen Fehlerbehandlungen Throwable abfangen. Sonst wird aus einem
Eingabefehler ein weißer Bildschirm.
Entfernte Funktionen und Erweiterungen
PHP 8.0 hat Funktionen entfernt, die in älterem Code noch vorkommen: each(),
create_function(), money_format(), get_magic_quotes_gpc() und die
Konstante FILTER_SANITIZE_MAGIC_QUOTES. Die XML-RPC-Erweiterung wurde aus dem
Kern entfernt und liegt seither in PECL.
Auch ältere Sprünge bleiben relevant. mcrypt fehlt seit PHP 7.2, die
mysql_*-Funktionen seit PHP 7.0. Wenn Ihre Anwendung von PHP 7.0 kommt und
diese Aufrufe nie ersetzt wurden, bricht sie bereits vor PHP 8.
Weitere Deprecations folgen mit jeder Minor-Version: strftime() und die
mhash-Familie ab 8.1, utf8_encode() und utf8_decode() ab 8.2, dynamische
Eigenschaften ab 8.2. Eine Liste der eigenen Treffer liefert eine Suche im Code
zusammen mit den Migrationshinweisen der Zielversion.
Abhängigkeiten setzen die Untergrenze
Bevor Sie über Code sprechen, klären Sie die Versionen. Composer-Pakete und Frameworks bestimmen, welche PHP-Version überhaupt möglich ist.
Laravel verlangt in Version 9 mindestens PHP 8.0, in Version 10 mindestens 8.1,
in Version 11 mindestens 8.2. Symfony 6.4 setzt PHP 8.1 voraus. Ein Paket, das
ext-mcrypt oder ext-xmlrpc benötigt, blockiert das Update unabhängig vom
eigenen Code.
Nützliche Befehle sind composer check-platform-reqs gegen die Zielumgebung,
composer why-not php 8.2 für blockierende Pakete und composer audit für
bekannte Sicherheitslücken. Mit config.platform.php in der composer.json
können Sie die Auflösung gegen die Zielversion testen, ohne sie bereits zu
installieren. Prüfen Sie auch die CI-Konfiguration: Eine Pipeline, die noch PHP
7.4 installiert, testet nicht die Zielversion.
Risiken vorher finden
Statische Analyse ist der günstigste Weg, Brüche zu finden. PHPStan und Psalm
prüfen Code gegen eine konfigurierte Zielversion. In der phpstan.neon setzen
Sie phpVersion auf die gewünschte Version und lassen die Analyse über die
kritischen Verzeichnisse laufen. Eine Baseline hält den Anfangsbefund fest,
damit neue Fehler sichtbar bleiben.
Rector kann zusätzlich zeigen, welche automatischen Änderungen ein Upgrade-Set
vornehmen würde. Ein Lauf mit --dry-run liefert einen Diff, ohne Dateien zu
verändern. Das ist kein Ersatz für Tests, aber ein guter Überblick.
Wenn eine Testsuite existiert, lassen Sie sie vor dem Update auf der alten Version laufen. Nur so wissen Sie, ob ein Fehler nach dem Update vom Code oder von der Umgebung kommt.
Testen Sie auf einer Staging-Umgebung mit produktionsnahen Daten. Gleiche PHP-Version, gleiche Erweiterungen, gleiche Konfiguration. Ein anonymisierter Datenbankauszug deckt Fälle auf, die synthetische Testdaten nicht erzeugen.
Definieren Sie außerdem ein Fehlerbudget für die ersten Tage nach dem Update. Beispiel: keine neuen fatalen Fehler, nicht mehr Warnungen als vorher.
Rechnungsrundung, Steuersätze, Rabatte, Zahlungs-Callbacks und Erstattungen sind die Stellen, an denen ein Typfehler echten Schaden verursacht. Diese Pfade brauchen Tests, bevor die Version wechselt.
Fazit
Der Sprung auf PHP 8 bricht selten die Startseite. Er bricht Sonderfälle, die niemand mehr kennt. Wer Zielversion, Abhängigkeiten und kritische Pfade vorher prüft, findet die meisten Probleme, solange sie noch günstig zu beheben sind.
Die passenden Schritte für eine gewachsene Anwendung beschreibt die PHP-Modernisierung. Warum Aufschub die Lage verschlechtert, steht im Beitrag technische Schulden erkennen. Und wenn bereits ein Neuschreiben im Raum steht, hilft der Vergleich modernisieren oder neu schreiben.
