der Praxisleitfaden fuer gewachsene Projekte
Eine Legacy Symfony 4 Migration scheitert selten an fehlendem Wissen ueber neue Features, sondern an der alten Bundle-Struktur, Sensio-Annotationen und fehlendem Flex. Wer die Reihenfolge kennt, in der Struktur, Konfiguration und Abhaengigkeiten modernisiert werden, bringt eine seit Jahren gewachsene Anwendung sicher auf einen aktuellen Symfony-Stand.
Inhaltsverzeichnis
- 1. Warum Legacy Symfony 4 Anwendungen besonders sind
- 2. Bestandsaufnahme: Bundle-Struktur und Abhaengigkeiten
- 3. Symfony Flex nachtraeglich einfuehren
- 4. Sensio-Annotationen durch PHP-Attribute ersetzen
- 5. Autowiring statt manueller Service-Definitionen
- 6. Der Stufenplan: 4 → 5 → 6 → 7 statt Direktsprung
- 7. Doctrine-Migrationen und Datenkonsistenz absichern
- 8. Typische Risiken bei der Legacy-Migration
- 9. Migrationsstrategien im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Legacy Symfony 4 Anwendungen besonders sind
Eine Legacy Symfony 4 Migration unterscheidet sich fundamental von einem regulaeren Minor-Upgrade, weil Symfony 4 aus einer Zeit stammt, in der Flex noch neu und viele heute selbstverstaendliche Konventionen noch nicht etabliert waren. Projekte, die 2018 oder 2019 gestartet wurden, tragen oft noch das alte AppBundle-Muster, manuell registrierte Services in YAML und das SensioFrameworkExtraBundle fuer Annotation-basiertes Routing. Diese Strukturen funktionieren technisch weiterhin, blockieren aber jeden weiteren Fortschritt in Richtung aktueller Symfony-Versionen.
Der zweite Grund, warum eine Legacy Symfony 4 Migration besondere Sorgfalt braucht: Zwischen Symfony 4 und Symfony 7 liegen drei Major-Versionsspruenge. Jeder davon hat eigene Breaking Changes, eigene Deprecation-Zyklen und eigene Config-Formate. Ein direkter Sprung von 4 auf 7 ohne Zwischenschritte ist technisch nicht vorgesehen und in der Praxis auch nicht sinnvoll, weil die Deprecation-Warnungen jeder Zwischenversion genau die Hinweise liefern, die man fuer eine sichere Migration braucht.
Der dritte Punkt betrifft das Team selbst: Viele Entwickler, die eine solche Anwendung heute betreuen, haben die urspruengliche Symfony-4-Struktur nicht selbst gebaut und kennen die historischen Gruende fuer bestimmte Entscheidungen nicht mehr. Eine Legacy Symfony 4 Migration ist deshalb auch eine Gelegenheit, verlorenes Architekturwissen durch dokumentierte, moderne Konventionen zu ersetzen, statt es weiter mitzuschleppen.
2. Bestandsaufnahme: Bundle-Struktur und Abhaengigkeiten
Der erste Schritt jeder Legacy Symfony 4 Migration ist eine ehrliche Bestandsaufnahme, keine Code-Aenderung. Wie viele Bundles registriert die Anwendung in AppKernel.php oder bundles.php? Welche davon sind eigene, welche stammen von Drittanbietern, und welche werden gepflegt? Ein Bundle ohne Commits seit drei Jahren ist ein klares Risiko fuer die Migration und sollte fruehzeitig als Ersatzkandidat markiert werden.
Parallel dazu lohnt eine Analyse der Verzeichnisstruktur. Symfony 4 fuehrte die Konvention src/ statt src/AppBundle/ ein, aber viele Projekte, die von Symfony 3 migriert wurden, behielten das alte AppBundle aus Traegheit bei. Fuer eine saubere Legacy Symfony 4 Migration muss diese Struktur zuerst begradigt werden, bevor an tiefere Abhaengigkeiten gedacht wird, weil viele nachfolgende Automatisierungswerkzeuge wie Rector von der Standard-Verzeichnisstruktur ausgehen.
# List all registered bundles, including deprecated Sensio bundles
grep -rn "Bundle::class" config/bundles.php app/AppKernel.php 2>/dev/null
# Find how many years since the last commit for a vendor bundle
composer show sensio/framework-extra-bundle
composer show knplabs/knp-menu-bundle
# Check for AppBundle-style legacy structure
find src -maxdepth 1 -type d -name "AppBundle"
3. Symfony Flex nachtraeglich einfuehren
Viele Legacy-Projekte wurden vor Flex gestartet oder haben Flex nie vollstaendig uebernommen. Ohne Flex fehlt die automatische Recipe-Verwaltung, und jedes neue Paket muss manuell konfiguriert werden, was gerade bei einer Legacy Symfony 4 Migration zu inkonsistenten Config-Strukturen fuehrt. Die nachtraegliche Einfuehrung von Flex ist moeglich, erfordert aber, dass composer.json und die Verzeichnisstruktur den Flex-Konventionen angepasst werden, bevor Flex fuer neue Pakete Recipes anwenden kann.
Der praktische Ablauf: Flex wird als Composer-Plugin installiert, danach werden bestehende Pakete einzeln mit composer recipes:install nachtraeglich mit ihren offiziellen Recipes versehen, sofern verfuegbar. Nicht jedes alte Paket hat eine passende Recipe, in diesem Fall bleibt die manuelle Konfiguration bestehen, aber neue Pakete profitieren ab diesem Zeitpunkt von automatischer Konfiguration. Diese Einfuehrung ist einer der wichtigsten Hebel jeder Legacy Symfony 4 Migration, weil sie den gesamten weiteren Weg beschleunigt.
# Install Flex as a Composer plugin in an existing Symfony 4 app
composer require symfony/flex
# Re-apply official recipes to already installed packages, where available
composer recipes
composer recipes:install symfony/monolog-bundle --force
# After Flex is active, config layout follows the config/packages/ convention
ls config/packages/
4. Sensio-Annotationen durch PHP-Attribute ersetzen
Das SensioFrameworkExtraBundle war in Symfony 4 der Standardweg fuer Annotation-basiertes Routing, @ParamConverter und Sicherheits-Annotationen. Seit Symfony 6 sind alle diese Funktionen als native PHP-Attribute im Symfony-Kern verfuegbar, und das Bundle selbst gilt als ueberholt. Eine Legacy Symfony 4 Migration muss diese Annotationen Schritt fuer Schritt durch PHP-Attribute ersetzen, weil das Bundle in neueren Symfony-Versionen nicht mehr aktiv weiterentwickelt wird und irgendwann inkompatibel wird.
Die Umstellung ist mechanisch, aber umfangreich: Jede @Route-Annotation wird zu einem #[Route]-Attribut, jede @ParamConverter-Nutzung wird durch natives Autowiring von Entities via #[MapEntity] ersetzt. Fuer grosse Controller-Verzeichnisse lohnt sich der Einsatz von Rector mit dem passenden Sensio-Rule-Set, das diese Transformation automatisiert und einen ueberpruefbaren Diff erzeugt, statt jede Datei von Hand zu bearbeiten.
<?php
declare(strict_types=1);
namespace App\Controller;
// BEFORE: SensioFrameworkExtraBundle annotations (Symfony 4 legacy style)
// use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
// use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
//
// /**
// * @Route("/product/{id}", name="product_show")
// * @ParamConverter("product", class="App\Entity\Product")
// */
// public function show(Product $product): Response { ... }
use App\Entity\Product;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class ProductController
{
// AFTER: native PHP attributes, no Sensio bundle required
#[Route('/product/{id}', name: 'product_show')]
public function show(#[MapEntity] Product $product): Response
{
return new Response($product->getName());
}
}
5. Autowiring statt manueller Service-Definitionen
Symfony 4 startete mit Autowiring als Standard, viele aeltere Projekte enthalten aber noch grosse services.yaml-Bloecke mit manuell aufgeloesten Konstruktor-Argumenten aus der Symfony-2- oder Symfony-3-Aera. Diese manuellen Definitionen sind fuer eine Legacy Symfony 4 Migration ein Wartungsrisiko, weil jede Aenderung an einem Konstruktor eine parallele Aenderung in der YAML-Datei erfordert, die leicht vergessen wird.
Die Bereinigung erfolgt am besten inkrementell: Zunaechst wird autowire: true und autoconfigure: true fuer den Standard-Namespace aktiviert, danach werden explizite Service-Definitionen einzeln entfernt und die Testsuite nach jeder Aenderung ausgefuehrt. Fuer Services mit mehreren Implementierungen desselben Interfaces bleiben explizite Definitionen mit Alias notwendig, aber der Grossteil der Legacy-Definitionen laesst sich ersatzlos streichen, sobald Autowiring korrekt konfiguriert ist.
6. Der Stufenplan: 4 → 5 → 6 → 7 statt Direktsprung
Der zentrale strategische Punkt jeder Legacy Symfony 4 Migration ist der Verzicht auf einen Direktsprung. Composer erlaubt technisch keine Versions-Constraint, die von 4.4 direkt auf 7.0 springt, ohne dass alle Zwischenversionen kompatibel aufgeloest werden koennen, aber selbst wenn es moeglich waere, waere es die falsche Strategie. Jede Symfony-Version zwischen 4 und 7 markiert bestimmte APIs als deprecated, bevor sie in der uebernaechsten Version entfernt werden. Ohne diese Zwischenschritte fehlen die Warnungen, die zeigen, welcher Code angepasst werden muss.
Der bewaehrte Ablauf: Erst auf Symfony 4.4 als letzte 4er-Version aktualisieren, alle Deprecations beheben, dann auf 5.4 LTS, erneut Deprecations beheben, dann auf 6.4 LTS, und erst zuletzt auf 7. Jede Zwischenversion wird mit einer laufenden Testsuite verifiziert, bevor der naechste Sprung erfolgt. Diese Legacy Symfony 4 Migration ueber vier Stationen dauert laenger als ein einzelner Upgrade-Schritt, produziert aber deutlich weniger unerwartete Fehler, weil jede Version fuer sich genommen ein ueberschaubares Deprecation-Delta hat.
# Stage 1: land on the last Symfony 4 release, fix all deprecations
composer require symfony/symfony:^4.4
SYMFONY_DEPRECATIONS_HELPER=weak php bin/phpunit
# Stage 2: move to Symfony 5.4 LTS
composer require symfony/symfony:^5.4
SYMFONY_DEPRECATIONS_HELPER=weak php bin/phpunit
# Stage 3: move to Symfony 6.4 LTS
composer require symfony/symfony:^6.4
SYMFONY_DEPRECATIONS_HELPER=weak php bin/phpunit
# Stage 4: final jump to Symfony 7
composer require symfony/symfony:^7.0
php bin/phpunit
7. Doctrine-Migrationen und Datenkonsistenz absichern
Eine Legacy Symfony 4 Migration betrifft nicht nur Code, sondern haeufig auch ein ueber Jahre gewachsenes Doctrine-Mapping mit inkonsistenten Namenskonventionen, veralteten Annotation-basierten Entity-Definitionen und Migrationsdateien, die teilweise manuell bearbeitet wurden. Vor der Code-Migration lohnt sich ein Abgleich zwischen dem tatsaechlichen Datenbankschema und den Doctrine-Migrationsdateien mit doctrine:migrations:diff, um versteckte Abweichungen aufzudecken, die sonst erst waehrend der Migration ueberraschen.
Doctrine-Entity-Annotationen im alten @ORM\Column-Stil funktionieren zwar weiterhin, sollten aber im Zuge der Legacy Symfony 4 Migration auf PHP-Attribute umgestellt werden, da Annotationen mittelfristig aus dem Doctrine-Kern verschwinden. Diese Umstellung laesst sich mit Rectors Doctrine-Annotation-Rule-Set weitgehend automatisieren und sollte parallel zur Sensio-Attribut-Migration erfolgen, da beide denselben Werkzeugkasten nutzen.
8. Typische Risiken bei der Legacy-Migration
Das groesste Risiko einer Legacy Symfony 4 Migration ist unzureichende Testabdeckung. Projekte aus der Symfony-4-Aera wurden oft ohne konsequente funktionale Tests gebaut, weil Testkultur in vielen Teams erst spaeter Einzug hielt. Ohne Tests laesst sich nicht verifizieren, ob eine Stufenmigration tatsaechlich funktionsgleich verlief, und Regressionen fallen erst durch Nutzer in Produktion auf.
Ein zweites haeufiges Risiko sind vergessene Cronjobs und Konsolenbefehle, die ausserhalb des HTTP-Request-Zyklus laufen und dadurch bei manuellen Tests im Browser leicht uebersehen werden. Ein drittes Risiko sind hartcodierte Pfade zu alten Bundle-Verzeichnissen in Deployment-Skripten, die nach der Verzeichnisbereinigung ins Leere laufen. Eine sorgfaeltige Legacy Symfony 4 Migration inventarisiert deshalb nicht nur Code und Bundles, sondern auch alle begleitenden Skripte und Automatisierungen.
9. Migrationsstrategien im direkten Vergleich
Die folgende Tabelle stellt drei moegliche Strategien fuer eine Legacy Symfony 4 Migration gegenueber und zeigt, wann welcher Ansatz sinnvoll ist.
| Strategie | Vorgehen | Risiko | Wann geeignet |
|---|---|---|---|
| Stufenmigration 4 → 5 → 6 → 7 | Jede Version einzeln, mit Deprecation-Fix | Niedrig | Standardfall bei aktiver Weiterentwicklung |
| Direktsprung mit manuellem Fix | Alle Breaking Changes auf einmal beheben | Hoch | Nur bei sehr kleinen, gut getesteten Apps |
| Strangler-Fig-Neuaufbau | Neue Symfony-7-App, Legacy schrittweise ablösen | Mittel, aber langwierig | Sehr grosse, stark verwucherte Legacy-Systeme |
Fuer die meisten Projekte ist die Stufenmigration die richtige Wahl, weil sie das Risiko klein haelt, ohne den Aufwand eines kompletten Neuaufbaus zu erfordern. Ein Strangler-Fig-Ansatz lohnt sich nur, wenn die Legacy Symfony 4 Migration ohnehin mit einer grundlegenden Architektur-Ueberarbeitung einhergehen soll.
Mironsoft
Legacy-Modernisierung und mehrstufige Symfony-Migrationen
Alte Symfony 4 Anwendung sicher modernisieren?
Wir uebernehmen die komplette Legacy Symfony 4 Migration, von der Bestandsaufnahme ueber die Stufenmigration bis zur Umstellung auf Flex, Attribute und Autowiring, mit voller Testabsicherung.
Legacy-Audit
Bestandsaufnahme von Bundles, Struktur und veralteten Abhaengigkeiten
Stufenmigration
Kontrollierter Weg von Symfony 4 ueber 5 und 6 bis Symfony 7
Testabsicherung
Funktionale Tests nachruesten, wo Testabdeckung fehlt
10. Zusammenfassung
Eine erfolgreiche Legacy Symfony 4 Migration beginnt mit einer ehrlichen Bestandsaufnahme der Bundle-Struktur, gefolgt von der nachtraeglichen Einfuehrung von Flex und der schrittweisen Ablösung von Sensio-Annotationen durch native PHP-Attribute. Autowiring ersetzt manuelle Service-Definitionen, und Doctrine-Mappings werden parallel von Annotationen auf Attribute umgestellt.
Der entscheidende strategische Punkt ist der Verzicht auf einen Direktsprung: Der Weg ueber 4.4, 5.4 LTS und 6.4 LTS bis Symfony 7 nutzt die Deprecation-Warnungen jeder Zwischenversion als Sicherheitsnetz. Wer diese Reihenfolge einhaelt und jede Stufe mit einer laufenden Testsuite absichert, bringt selbst stark gewachsene Legacy-Anwendungen zuverlaessig auf einen modernen Symfony-Stand.
Legacy Symfony 4 Migration — Das Wichtigste auf einen Blick
Bestandsaufnahme zuerst
Bundle-Liste, Verzeichnisstruktur und Abhaengigkeits-Alter dokumentieren, bevor Code angefasst wird.
Flex nachruesten
Automatische Recipe-Verwaltung fuer alle zukuenftigen Pakete, auch nachtraeglich moeglich.
Stufenplan statt Direktsprung
4.4 → 5.4 LTS → 6.4 LTS → 7, jede Stufe mit gruener Testsuite abschliessen.
Attribute statt Annotationen
Sensio- und Doctrine-Annotationen auf native PHP-Attribute umstellen, moeglichst per Rector.