Zum Inhalt springen

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.

Zuletzt geprüft:

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_errors falsch steht.
  • Vergleiche zwischen Zeichenkette und Zahl verhalten sich anders. 0 == "foo" ist seit 8.0 false.
  • 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.

entferntseitErsatz
ereg, eregi, split7.0preg_match, preg_split
mysql_*7.0mysqli_* oder PDO
create_function8.0Closure
each8.0foreach
money_format8.0NumberFormatter
get_magic_quotes_gpc8.0entfällt
${var} in Zeichenketten8.2 veraltet{$var}
utf8_encode, utf8_decode8.2 veraltetmb_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

  1. Aktuelle Version feststellen und die Zwischenschritte planen. Von 5.6 aus: 7.0, 7.4, 8.0, dann weiter.
  2. Statische Analyse auf dem aktuellen Stand, Baseline erzeugen.
  3. Rector im Probelauf, Ergebnis prüfen, dann anwenden.
  4. Version wechseln, Testlauf mit voller Fehleranzeige.
  5. Abhängigkeiten aktualisieren – oft sind Bibliotheken das eigentliche Hindernis.
  6. 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.