Typisierung nachrüsten: strict_types in Legacy-PHP einführen

PHP strict_types nachträglich in eine Legacy-Anwendung einzuführen ist möglich – wenn Sie es datei-für-datei angehen statt mit dem großen Wurf. So gehen Sie ohne Ausfall vor.

Eine Anwendung läuft seit acht Jahren stabil im Produktivbetrieb, verdient jeden Tag Geld – und trotzdem heißt es intern, der Code sei "eine Katastrophe". Das ist selten wahr. Ein System, das so lange läuft, ist der Beweis, dass die Fachlogik funktioniert. Was fehlt, ist meistens nicht die Logik, sondern die Absicherung drumherum. Genau hier setzt Typisierung an. Dieser Artikel zeigt, wie Sie PHP strict_types in einer gewachsenen Codebasis nachrüsten, ohne die Produktion zu riskieren.

Was bewirkt declare(strict_types=1) in PHP überhaupt?

Die Direktive declare(strict_types=1) schaltet für die jeweilige Datei den strikten Typmodus ein. PHP hört dann auf, Skalartypen an Funktionsgrenzen stillschweigend umzuwandeln – Sie bekommen einen TypeError statt einer stillen Konvertierung.

Der Unterschied ist konkret. Ohne strikten Modus akzeptiert eine Funktion function berechne(int $tage) auch den String "30" und macht daraus klaglos die Zahl 30. Schlimmer: "30 Tage" wird im Coercive-Modus je nach PHP-Version zu 30 (mit Warnung) oder schlägt fehl. Mit strict_types=1 ist der Fall eindeutig – ein String, wo ein int erwartet wird, ist ein Fehler. Und ein Fehler, den Sie im Test sehen, ist einer, den Ihr Kunde nicht in der Rechnung sieht.

Zwei Dinge sind entscheidend zu verstehen. Erstens: Die Deklaration gilt nur für die Datei, in der sie steht, und zwar für die Aufrufe, die aus dieser Datei heraus erfolgen. Zweitens: Sie muss die allererste Anweisung der Datei sein, direkt nach <?php. Beide Eigenschaften sind bei einer Migration Ihr Vorteil, kein Nachteil.

Warum strict_types in Legacy-PHP datei-für-datei eingeführt werden kann

Weil die Direktive dateilokal wirkt, müssen Sie nicht das gesamte Projekt auf einen Schlag umstellen. Sie können eine einzelne Klasse strikt machen, während der Rest der Anwendung im gewohnten Coercive-Modus weiterläuft. Das nimmt der Migration ihren Schrecken.

Das ist der zentrale Punkt, den viele übersehen und deshalb vorschnell über eine Neuentwicklung nachdenken. Eine Neuentwicklung ist fast immer die teuerste und riskanteste Option – Sie werfen funktionierende, über Jahre erprobte Fachlogik weg, um sie mit neuen, noch ungetesteten Bugs nachzubauen. Das schrittweise Nachrüsten von Typen erreicht dasselbe Ziel bei einem Bruchteil des Risikos.

Ein sinnvoller Schnitt: Beginnen Sie bei neuen Dateien. Jede Klasse, die ab heute entsteht, bekommt strict_types=1 als feste Regel. Danach nehmen Sie sich die Bereiche vor, die Sie ohnehin gerade anfassen. So wächst die Abdeckung mit der normalen Arbeit, ohne ein separates Großprojekt.

Wie gehen Sie die Migration Schritt für Schritt an?

  1. Baseline mit statischer Analyse schaffen. Führen Sie PHPStan oder Psalm ein, zunächst auf einem niedrigen Level (PHPStan Level 0–2). Erzeugen Sie eine Baseline-Datei, die alle bestehenden Meldungen "einfriert". Ab jetzt darf nichts Neues dazukommen, das Alte arbeiten Sie nach und nach ab.
  2. Typen ergänzen, bevor Sie strikt schalten. Fügen Sie Parameter-, Rückgabe- und Property-Typen hinzu. In dieser Phase hilft ein Werkzeug wie Rector enorm, das viele Typen automatisch aus vorhandenen Docblocks und der Nutzung ableiten kann.
  3. strict_types pro Datei aktivieren. Erst wenn eine Datei sauber typisiert ist und die Tests grün sind, setzen Sie declare(strict_types=1) ganz oben.
  4. Aufrufer prüfen. Der TypeError entsteht am Aufruf aus der strikten Datei heraus. Prüfen Sie also, welche Werte tatsächlich hineingegeben werden – oft aus Request-Parametern oder der Datenbank, wo alles als String ankommt.

Rector kann diesen Prozess mit der Regel DeclareStrictTypesRector teilweise automatisieren. Lassen Sie es aber nie blind über das ganze Projekt laufen. Automatisierung ist hier ein Assistent, kein Autopilot.

Wo liegen die typischen Fallstricke?

Der häufigste Schmerzpunkt sind die Ränder Ihrer Anwendung. Aus $_GET, $_POST, aus PDO ohne PDO::ATTR_EMULATE_PREPARES = false und aus vielen Alt-APIs kommen Zahlen als Strings. Eine strikt typisierte Methode, die ein int erwartet, wirft dann sofort einen TypeError.

Das ist keine Schikane von PHP – es legt offen, dass an dieser Stelle nie sauber validiert wurde. Die richtige Antwort ist nicht, den strikten Modus wieder abzuschalten, sondern an der Grenze explizit zu casten oder zu validieren: $tage = (int) $request->get('tage');. Sie ziehen die Typumwandlung damit an genau eine kontrollierte Stelle, statt sie unsichtbar über das ganze System zu verteilen.

Weitere Punkte, auf die Sie achten sollten:

  • Frameworks: Symfony liefert seine eigenen Klassen längst mit strict_types aus, Laravel eher nicht. In beiden Fällen betrifft Ihre Umstellung nur Ihren eigenen Code – das Framework läuft unverändert weiter.
  • float und int: Auch im strikten Modus wird ein int weiterhin zu float erweitert, wenn ein float erwartet wird. Das ist Absicht und kein Bug.
  • Interne PHP-Funktionen: Der strikte Modus gilt auch für Aufrufe von Built-in-Funktionen aus Ihrer Datei. Code, der sich bisher auf lockere Umwandlung verlassen hat, fällt hier auf.

Warum sich der Aufwand für PHP strict_types lohnt

Typisierung ist keine Kosmetik. Sie verwandelt eine ganze Klasse von Laufzeitfehlern in Fehler, die schon bei der statischen Analyse oder spätestens im Test auffallen. Ihre IDE gibt bessere Autovervollständigung, PHPStan findet echte Bugs, und neue Entwickler verstehen den Code schneller, weil die Datentypen dokumentiert sind – und zwar verbindlich, nicht nur in einem veralteten Kommentar.

Vor allem aber senken Sie das Risiko jeder künftigen Änderung. Genau das ist der Punkt bei Legacy-Systemen: Nicht die Fachlogik ist das Problem, sondern die Angst, etwas anzufassen. Typen nehmen einen Teil dieser Angst weg, weil das System Ihnen sagt, wenn Sie einen Vertrag verletzen.

Praxis-Tipp zum Schluss

Fangen Sie klein an und seien Sie ehrlich zum eigenen Zeitplan. Setzen Sie strict_types=1 ab heute in jede neue Datei, richten Sie eine PHPStan-Baseline ein, und rüsten Sie bestehende Dateien nur dann um, wenn Sie sie ohnehin bearbeiten. Nach einem halben Jahr ist ein überraschend großer Teil der Codebasis abgedeckt – ohne dass jemand ein Migrationsprojekt genehmigen musste. Ein Big-Bang-Umbau über Nacht dagegen bringt Ihnen vor allem eine lange Nacht.

Wenn Sie bei genau dieser schrittweisen Absicherung eines gewachsenen PHP-Systems eine zweite, erfahrene Hand brauchen, ist das eines der Themen, bei denen LegacyWerk unterstützt.

strict_types schrittweise einführen 1. Baseline PHPStan / Psalm 2. Typen Rector ergaenzt 3. strict_types pro Datei aktiv 4. Aufrufer casten / pruefen Vorher (coercive) berechne("30 Tage") stille Umwandlung, spaeter Bug Nachher (strict) berechne((int) $tage) TypeError frueh im Test

Vollmodernisierung: Festpreis nach Befund, 2–6 Monate

Kompatibilität plus Struktur: Tests, saubere Schichten, Datenbank-Integrität, CI/CD – damit Ihr Team wieder bauen kann. Sie schildern mir Ihr System in drei kurzen Schritten, ich antworte mit einer ehrlichen Einschätzung und einem Festpreis.

Vollmodernisierung anfragen