PHP-8-Migration für Altprojekte: die Bruchstellen der Reihe nach
Entfernte Funktionen, geänderte Fehlerbehandlung und strengere Typprüfung: welche Muster in gewachsenem PHP-Code brechen und wie du sie vor dem Versionssprung findest.
Das Wichtigste
- Der grösste Einzelbruch in PHP 8.0: Konstruktoren mit dem Klassennamen und viele Warnungen, die zu Fehlern wurden.
- Zugriff auf einen nicht existierenden Array-Schlüssel ist seit 8.0 eine Warnung – in Ausgaben sichtbar, wenn
display_errorsfalsch steht. - Vergleiche zwischen Zeichenkette und Zahl verhalten sich anders.
0 == "foo"ist seit 8.0false. - Statische Analyse findet die meisten Bruchstellen, bevor sie produktiv auftreten. Ein Testlauf mit voller Fehleranzeige findet den Rest.
Bei einem Versionssprung geht selten die Anwendung als Ganzes kaputt. Es sind einzelne Stellen, die vorher eine Warnung erzeugten und jetzt abbrechen – und die findet man am besten, bevor sie produktiv auffallen.
Die Bruchstellen, die zählen
Konstruktoren im alten Stil. Bis PHP 7 war eine Methode mit dem Klassennamen der Konstruktor. Seit 8.0 ist sie eine gewöhnliche Methode – das Objekt wird erzeugt, aber nichts initialisiert:
class vlibTemplate {
// wurde bis PHP 7 als Konstruktor aufgerufen, seit 8.0 nicht mehr
function vlibTemplate($file) { … }
// richtig
public function __construct($file) { … }
}
Der Fehler ist besonders unangenehm, weil er keine Ausnahme auslöst. Das Objekt existiert, seine Eigenschaften sind leer, und der Fehler taucht irgendwo später auf.
Entfernte Funktionen.
| entfernt | seit | Ersatz |
|---|---|---|
ereg, eregi, split | 7.0 | preg_match, preg_split |
mysql_* | 7.0 | mysqli_* oder PDO |
create_function | 8.0 | Closure |
each | 8.0 | foreach |
money_format | 8.0 | NumberFormatter |
get_magic_quotes_gpc | 8.0 | entfällt |
${var} in Zeichenketten | 8.2 veraltet | {$var} |
utf8_encode, utf8_decode | 8.2 veraltet | mb_convert_encoding |
Warnungen wurden zu Fehlern. In PHP 8.0 sind viele Zustände, die vorher eine Warnung erzeugten, zu Error geworden: Methodenaufruf auf null, ungültige Argumentanzahl bei internen Funktionen, Division durch null.
// PHP 7: Warning, Ergebnis INF
// PHP 8: DivisionByZeroError
$x = 1 % 0;
Zugriff auf nicht existierende Array-Schlüssel ist seit 8.0 eine Warnung statt einer Notiz. Wenn display_errors produktiv auf On steht, landet sie mitten in der Ausgabe – und dort, wo HTTP-Header noch nicht gesendet wurden, bricht sie den Seitenaufbau.
// vorher: Notice, leer
// jetzt: Warning
echo $daten['optional'];
// robust
echo $daten['optional'] ?? '';
Vergleiche zwischen Zeichenkette und Zahl verhalten sich anders:
// PHP 7: true (die Zeichenkette wurde zu 0)
// PHP 8: false (die Zahl wird zur Zeichenkette)
var_dump(0 == 'foo');
// unverändert und immer die bessere Wahl
var_dump(0 === 'foo'); // false
Das betrifft vor allem Code, der Formulareingaben mit == prüft. Eine Anmeldeprüfung, die auf diesem Verhalten beruhte, ist mit dem neuen Verhalten sicherer – aber Code, der sich darauf verliess, ändert seine Bedeutung.
Bruchstellen finden, bevor sie auftreten
Statische Analyse.
composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse --level=0 src
Level 0 meldet bereits entfernte Funktionen, unbekannte Klassen und falsche Argumentzahlen. Für eine Migration ist das der richtige Startpunkt – höhere Level bringen Codequalität, nicht Versionskompatibilität.
Automatisch umschreiben.
composer require --dev rector/rector
vendor/bin/rector process src --dry-run
Rector kennt die Regeln jeder Version und schreibt viele Muster selbst um: alte Konstruktoren, each-Schleifen, entfernte Funktionen. Mit --dry-run erst ansehen, was es tun würde.
Testlauf mit voller Fehleranzeige. In einer Kopie der Anwendung:
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/tmp/php-migration.log');
Dann die wichtigsten Abläufe durchgehen: Startseite, Formulare, Login, Bestellstrecke, Verwaltungsbereich. Anschliessend das Log auswerten:
sort /tmp/php-migration.log | uniq -c | sort -rn | head -40
Die Ausgabe ins Log statt auf den Bildschirm ist wichtig – so verfälschen die Meldungen nicht die Seiten, die du gerade testest.
Vorgehen in Schritten
- Aktuelle Version feststellen und die Zwischenschritte planen. Von 5.6 aus: 7.0, 7.4, 8.0, dann weiter.
- Statische Analyse auf dem aktuellen Stand, Baseline erzeugen.
- Rector im Probelauf, Ergebnis prüfen, dann anwenden.
- Version wechseln, Testlauf mit voller Fehleranzeige.
- Abhängigkeiten aktualisieren – oft sind Bibliotheken das eigentliche Hindernis.
- Erst dann produktiv umstellen, und die alte Version für einen schnellen Rückweg bereithalten.
Punkt 5 wird gern unterschätzt. Eine Anwendung kann sauber laufen, während eine eingebundene Bibliothek auf der neuen Version nicht mehr funktioniert und seit Jahren nicht gepflegt wird. Dann ist die eigentliche Aufgabe nicht die Migration, sondern der Ersatz dieser Bibliothek.
Für Template-Engines steht das im Detail in den jeweiligen Anleitungen – etwa bei vlibTemplate, wo genau diese Konstruktor-Falle zuschlägt.
Häufige Fragen
Kann ich direkt von PHP 5.6 auf 8.3 springen?
Technisch ja, praktisch ist es riskant. Zwischen den Versionen liegen mehrere Runden entfernter Funktionen und geänderter Semantik. Der beherrschbare Weg führt über Zwischenschritte, bei denen du nach jedem Sprung testest – dann weisst du bei einem Fehler, welche Änderung ihn verursacht hat.
Wie finde ich Bruchstellen, ohne alles durchzulesen?
Mit statischer Analyse. PHPStan oder Psalm melden entfernte Funktionen, falsche Argumenttypen und fragwürdige Vergleiche, ohne den Code auszuführen. Ergänzend hilft Rector, das viele Muster automatisch umschreibt.
Passend dazu
- Coding Standard: Konventionen, die Wartung sparen
Namensgebung, Einrückung, Kommentare und Dateiaufbau - als prüfbare Regeln statt als Geschmacksfrage.
- vlibTemplate: Aufbau, Syntax und Beispiele
Platzhalter, Schleifen, Bedingungen und Includes der klassischen Engine - inklusive Hinweisen zum Betrieb auf aktuellen PHP-Versionen.