Ein Symfony-Upgrade scheitert selten an Symfony selbst. Es scheitert an Doctrine. Genauer: an den vielen kleinen Annahmen, die sich über Jahre in eine gewachsene Anwendung geschlichen haben und die niemand mehr dokumentiert hat. Die Fachlogik läuft seit Jahren korrekt durch, die Bestellungen kommen an, die Rechnungen stimmen. Kaputt ist nicht das System, kaputt sind die Verträge zwischen ORM-Version und Anwendungscode, die beim Upgrade neu ausgehandelt werden.
Dieser Artikel geht die häufigsten Fallstricke beim Symfony Doctrine Upgrade durch und zeigt, wie Sie jeden davon kontrolliert entschärfen, statt ihn in Produktion zu entdecken.
Warum bricht Doctrine beim Symfony-Upgrade zuerst?
Doctrine ORM und Doctrine DBAL sind eigenständige Bibliotheken mit eigenem Versionszyklus, die über das DoctrineBundle an Symfony angebunden werden. Ein Sprung von Symfony 4 auf 6 zieht fast immer einen Sprung von DBAL 2 auf 3 oder 4 und von ORM 2 auf 3 nach sich.
Das Problem: Diese Major-Sprünge enthalten Verhaltensänderungen, die der Compiler nicht anmeckert. Ihr Code kompiliert weiter, die Typen passen weiter, aber die Ergebnisse ändern sich zur Laufzeit. Das ist die gefährlichste Klasse von Änderung, weil sie erst beim echten Datensatz auffällt.
Welche Doctrine-Änderungen brechen am häufigsten?
Drei Bereiche verursachen in der Praxis den Großteil der Probleme bei einem Symfony Doctrine Upgrade. Sie sind tückisch, weil sie funktionierenden Code stillschweigend anders interpretieren.
1. Geänderte Typ-Rückgaben in DBAL
DBAL 3 hat aufgehört, Datenbankwerte automatisch in PHP-Typen zu casten. Eine Spalte vom Typ INT kam früher als PHP-Integer zurück, jetzt kommt sie in vielen Konstellationen als String. Wenn Ihr Code irgendwo einen strikten Vergleich macht, kippt die Logik lautlos:
if ($row['status'] === 1)war jahrelang wahr. Nach dem Upgrade ist$row['status']der String"1"und der Vergleich ist plötzlich falsch.
Der Fehler wirft keine Exception. Er zeigt einfach den falschen Status an oder überspringt einen Verarbeitungsschritt. Suchen Sie gezielt nach strikten Vergleichen (===, !==) auf DBAL-Rohergebnissen und nach switch-Blöcken über solche Werte.
2. Entfernte und umbenannte Query-Methoden
Viele bequeme Kurzformen sind verschwunden. fetch(), fetchAll() und fetchColumn() auf dem Result-Objekt wurden durch explizite Methoden wie fetchAssociative(), fetchAllAssociative() und fetchOne() ersetzt. Das ist mechanisch, aber gefährlich, wenn eine alte und eine neue Methode ähnlich heißen und subtil anderes Verhalten haben.
Auf ORM-Seite betrifft es unter anderem das Laden über den Repository: die Signaturen rund um find() und die Hydration-Modi sind strenger geworden. Blindes Suchen-und-Ersetzen ist hier riskant, weil eine falsch gewählte Fetch-Methode wieder nur zur Laufzeit auffällt.
3. Annotations, die zu Attributes werden
Ab ORM 3 sind die klassischen Doctrine-Annotations in Docblock-Kommentaren nicht mehr unterstützt. Das Mapping muss auf PHP-8-Attribute oder auf XML umgestellt werden. Solange die Annotations noch als Kommentar im Code stehen und das Mapping nicht greift, hat Doctrine keine Ahnung mehr, wie Ihre Entitäten aussehen.
Wie stellen Sie ein Symfony Doctrine Upgrade sicher durch?
Die Antwort ist nicht Mut, sondern Reihenfolge und ein Sicherheitsnetz. Der Ablauf, der sich in der Praxis bewährt, sieht so aus:
- Erst absichern, dann anfassen. Bevor Sie eine Version anheben, brauchen Sie Tests rund um die kritischen Datenpfade. Nicht die ganze Anwendung, sondern die Stellen, an denen Geld, Status oder Berechtigungen berechnet werden. Ohne diese Tests upgraden Sie blind.
- DBAL vor ORM. Heben Sie zuerst DBAL an und stabilisieren Sie die Typ- und Fetch-Änderungen, bevor Sie das ORM anfassen. Zwei Major-Sprünge gleichzeitig sind kaum noch debugbar.
- Ein Schritt pro Commit. Deprecations auflösen, Version anheben, Tests grün, committen. Wenn Sie fünf Dinge gleichzeitig ändern und die Tests rot werden, wissen Sie nicht, welches.
- Deprecations vorher abernten. Symfony und Doctrine loggen Deprecations zur Laufzeit. Lassen Sie die alte Version mit aktiviertem Deprecation-Logging über Ihre Tests und idealerweise über echten Traffic laufen und arbeiten Sie die Liste ab, solange die alte Version noch stabil ist.
Welche Rolle spielen Migrations und das Schema?
Ein unterschätzter Fallstrick: Nach dem Upgrade generiert doctrine:schema:update --dump-sql plötzlich einen Berg von Änderungen, obwohl sich Ihr Schema nicht geändert hat. Der Grund ist meist, dass die neue Doctrine-Version Typen anders auf die Datenbank abbildet, etwa bei JSON-, BOOLEAN- oder DATETIME-Spalten.
Führen Sie dieses generierte SQL niemals ungeprüft aus. Prüfen Sie jede Zeile. Oft ist es kosmetisch und darf in einer Baseline-Migration eingefroren werden. Manchmal steckt ein echter, riskanter ALTER TABLE auf einer großen Tabelle darin, der in Produktion die Tabelle sperren würde.
Wann lohnt sich das Upgrade und wann nicht?
Ein System, das seit Jahren zuverlässig läuft, ist kein technischer Schuldschein, den man wegwerfen muss. Es ist der laufende Beweis, dass die Fachlogik funktioniert. Genau diese Logik ist beim Upgrade fast nie das Problem. Das Problem ist der Code drumherum: die Query-Abstraktionen, die Typ-Annahmen, das Mapping.
Deshalb ist die Neuentwicklung auf grüner Wiese in den meisten Fällen die teuerste und riskanteste Option. Sie werfen dabei jahrelang gehärtete, erprobte Fachlogik weg, um sie danach mühsam nachzubauen und alle bereits gelösten Sonderfälle erneut zu entdecken. Ein sauber durchgeführtes Symfony Doctrine Upgrade behält den funktionierenden Kern und tauscht nur die veraltete Hülle aus.
Praxis-Tipp zum Schluss
Wenn Sie nur eine Sache mitnehmen: Aktivieren Sie das Deprecation-Logging auf der aktuellen, noch laufenden Version und lassen Sie es über echten Traffic mitschreiben, bevor Sie überhaupt eine Version anheben. Diese Log-Liste ist Ihre ehrlichste To-do-Liste. Sie zeigt Ihnen genau, welche Stellen die neue Doctrine-Version anders behandeln wird, und zwar auf Basis dessen, was Ihre Anwendung wirklich tut, nicht was die Dokumentation vermutet.
Genau bei solchen Upgrades, bei denen die Fachlogik erhalten bleiben soll und nur der veraltete Unterbau kontrolliert getauscht wird, unterstützt LegacyWerk.