Symfony 6.4 auf 7.4 LTS upgraden: Eine Schritt-für-Schritt-Anleitung für Produktions-Apps

#symfony 6.4 auf 7.4 upgrade

Gründer & Lead Developer

Experte für Softwareentwicklung und Legacy-Code-Optimierung

LinkedIn

Wenn dein Team Symfony 6.4 betreibt und den Umstieg auf 7.4 plant, ist die gute Nachricht: Du upgradest zwischen zwei Long-Term-Support-Releases, und das ist der einfachste Pfad, den Symfony bietet. Dies ist keine Anleitung dazu, was sich zwischen den beiden Versionen geändert hat. Das haben wir in unserem allgemeinen Symfony-7-Upgrade-Leitfaden behandelt. Dies ist die praktische Abfolge: die Befehle, die du ausführen musst, die Reihenfolge, in der du sie ausführst, und die Checks, die dir zeigen, ob die Anwendung tatsächlich bereit für Produktion auf 7.4 ist.

Ein Upgrade von 6.4 auf 7.4 ist ein Projekt mit bekannter Form. Das meiste Risiko sitzt an drei Stellen: Deprecations, die dein Team ignoriert hat, während sie nur Warnungen waren, Third-Party-Bundles, die noch nicht mit Symfony 7 mitgezogen sind, und Flex-Recipe-Drift, der sich über die Jahre angesammelt hat, in denen die Anwendung auf 6.4 läuft. Behandle diese drei in der richtigen Reihenfolge, und der eigentliche Versionssprung wird fast mechanisch.

Schritt 1: Null Deprecations auf 6.4 erreichen

Fasse composer.json noch nicht an. Jede in Symfony 7.0 entfernte API ist in 6.4 noch vorhanden, nur als deprecated markiert - das heißt, 6.4 ist der Ort, an dem du das Upgrade vorbereitest, ohne Produktion zu brechen. Route Deprecation-Hinweise in eine eigene Log-Datei, damit sie nicht im allgemeinen Application-Log untergehen:

# config/packages/monolog.yaml
monolog:
    channels: ['deprecation']
    handlers:
        deprecation:
            type: stream
            path: '%kernel.logs_dir%/deprecation.log'
            channels: ['deprecation']
            level: info

Lass die Anwendung ein paar Tage unter normalen Traffic-Mustern laufen, in Dev, in CI und idealerweise in Staging, und triagiere dann das Log. Der schnellste Weg, die Deprecations auf Konfigurationsebene zu finden, ist:

bin/console debug:container --deprecations

Das Beheben eines schlechten Service-Alias oder einer veralteten Bundle-Konfiguration räumt hier oft Dutzende Warnungen auf einmal auf - fang also dort an, bevor du dich in den Anwendungscode vertiefst. Die Deprecations, die nach diesem Durchgang übrig bleiben, fallen in eine kurze Liste, die in fast jeder 6.4-Codebasis auftaucht: Controller oder Services, die noch Symfony\Component\Security\Core\Security statt Symfony\Bundle\SecurityBundle\Security type-hinten, Custom-Authenticator, die auf dem Security-System vor 5.3 aufbauen und auf AbstractAuthenticator umziehen müssen, und Services, die Abhängigkeiten zur Laufzeit aus dem Container ziehen statt per Constructor Injection. Keine davon ist einzeln schwierig. Die Arbeit liegt darin, sie alle zu finden - deshalb ist das Deprecation-Log der richtige Startpunkt, nicht eine Code-Suche.

Behandle null Deprecations unter realistischem Traffic als Exit-Kriterium für diesen Schritt, nicht als groben Richtwert. Eine Handvoll hartnäckiger Warnungen, die übrig bleiben, ist genau der Weg, wie aus einem 6.4-auf-7.4-Upgrade eine Woche nach dem Deploy ein Produktionsvorfall wird.

Schritt 2: Rector ausführen, bevor du etwas von Hand anfasst

Die Umwandlung von Annotations zu Attributes, die Umbenennung veralteter Methoden und die meisten Interface-Migrationen sind mechanisch - das heißt, Rector sollte sie erledigen, nicht ein Entwickler mit einem Find-and-Replace-Skript. Aktuelle Rector-Versionen lesen deine composer.json und wählen automatisch die passenden Symfony- und Doctrine-Rule-Sets:

// rector.php
use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withPaths([
        __DIR__ . '/src',
        __DIR__ . '/tests',
    ])
    ->withPhpSets(php82: true)
    ->withAttributesSets(symfony: true, doctrine: true)
    ->withComposerBased(symfony: true, doctrine: true);

Führe es auf einem eigenen Branch aus und reviewe den Diff in Chunks, gruppiert nach Rule-Set, statt als einen riesigen Commit. Eine @Route-Annotation, die zu #[Route] wird, ist eine sichere, unspektakuläre Änderung, die du in großen Mengen abnicken kannst. Eine geänderte Methodensignatur auf einem Voter oder einem Event-Subscriber ist es wert, Zeile für Zeile gelesen zu werden, denn Rector wendet den neuen Typ korrekt an, kann dir aber nicht sagen, ob deine Business-Logik unter den strengeren Typen, die Symfony 7 erzwingt, noch Sinn ergibt. Unser vollständiger Walkthrough zum Ausführen von Rector gegen eine Legacy-PHP-Codebasis deckt die Chunking-Strategie und die Rule-Sets ab, die es wert sind, separat statt alle auf einmal ausgeführt zu werden.

Schritt 3: Jedes Bundle auf Symfony-7-Kompatibilität auditieren

Bevor du auch nur einen Versions-Constraint änderst, prüfe, was in deiner composer.json keinen Pfad zu Symfony 7 hat. Führe ein Dry-Run-Update aus, um zu sehen, wo der Dependency-Graph bricht:

composer why-not symfony/symfony 7.4
composer outdated --direct

Für jedes Bundle, das das Update blockiert, gibt es drei Ergebnisse: Eine neuere Version desselben Bundles unterstützt Symfony 7 und du aktualisierst darauf, eine gepflegte Alternative existiert und du migrierst dorthin, oder das Bundle wird nicht mehr gepflegt und du forkst es mit einem gelockerten Versions-Constraint als Übergangslösung, während du seine Entfernung planst. Ende 2026 sind die Bundles, die ein Symfony-7-Update noch blockieren, fast immer der dritte Fall - was selbst eine nützliche Information ist. Eine ungepflegte Abhängigkeit in deiner composer.json ist ein Risiko unabhängig von diesem Upgrade, und das ist der Moment, in dem du es entdeckst.

Führe eine kurze schriftliche Liste jedes Bundles, das dieser Schritt berührt. Sie wird die Grundlage für die Pull-Requests im nächsten Schritt, und sie ist das Erste, nach dem ein zweiter Entwickler fragen wird, wenn das Upgrade stockt.

Schritt 4: Der Versionssprung

Mit geleertem Deprecation-Log, ausgeführtem Rector und bestätigter Bundle-Kompatibilität ist der eigentliche Sprung klein. Symfony 7 erfordert PHP 8.2 über jedes 7.x-Release hinweg, einschließlich 7.4 - bestätige also zuerst deine Laufzeitumgebung:

php -v

Wenn du noch auf PHP 8.1 oder früher bist, mach dieses Upgrade als eigenes Deploy, bevor du Symfony anfasst, damit sich ein Problem auf eine Änderung statt auf zwei gleichzeitig zurückführen lässt. Sobald die Laufzeitumgebung bereit ist, aktualisiere die Constraints:

"extra": {
    "symfony": {
        "require": "7.4.*"
    }
}
composer update "symfony/*" --with-all-dependencies

Das ist der Schritt, bei dem am ehesten ein Bundle auftaucht, das du in Schritt 3 übersehen hast. Wenn er fehlschlägt, nennt der Fehler das konfliktverursachende Paket, und du gehst zurück und löst es auf dieselbe Weise.

Schritt 5: Flex-Recipes einzeln aktualisieren

Flex-Recipes driften von ihren ursprünglichen Defaults ab, während eine Codebasis altert, was normal ist und für sich genommen kein Problem. Es wird während eines Upgrades zur Friktion, weil composer recipes:update das neue Recipe als Patch gegen deine aktuellen Dateien anwendet und dir überlässt, die Konflikte aufzulösen, ähnlich wie bei einem Merge:

composer recipes
composer recipes:update symfony/framework-bundle
composer recipes:update symfony/security-bundle

Mach ein Bundle pro Commit und reviewe jeden Diff, statt ihn zu akzeptieren. Die Dateien, die am ehesten eine Abstimmung brauchen, sind config/packages/framework.yaml (Session-Handler- und CSRF-Defaults haben sich geändert), config/packages/security.yaml (strengere Access-Control-Syntax, Anonymous-User-Handling entfernt) und config/packages/doctrine.yaml (konservativere auto_mapping-Defaults). Unser Leitfaden zu Flex-Recipes und Paket-Upgrades geht den Abstimmungsprozess für eine stark angepasste security.yaml durch, die in der Praxis die meiste Nacharbeit verursacht.

Schritt 6: Was lautlos bricht, wenn du die Vorarbeit überspringst

Zwei Entfernungen in Symfony 7 werfen keinen klaren Fehler; sie hören einfach auf, wie erwartet zu funktionieren. Das Paket doctrine/annotations und annotation-basiertes Routing sind weg, also wird jede @Route oder @ParamConverter, die dein Rector-Durchgang übersehen hat (meist in einem weniger besuchten Teil der Codebasis, einem Konsolenbefehl oder einem internen Admin-Panel), lautlos nicht registriert, statt beim Deploy zu crashen. Das Security-System vor 5.3, einschließlich jeder Bridge-Klasse, die alte und neue Authenticator koexistieren ließ, ist ebenfalls vollständig entfernt, also wird ein Custom-Login-Flow, der nie zu AbstractAuthenticator migriert wurde, niemanden mehr authentifizieren, sobald 7.4 live ist. Beide sind Gründe, in Staging einen vollständigen Route- und Permission-Smoke-Test durchzuführen, statt sich allein auf die Testsuite zu verlassen, denn Routing- und Security-Konfigurationsprobleme sind genau die Art von Sache, die ein enger Unit-Test übersieht.

Schritt 7: Teststrategie und Validierung

Bevor du das Upgrade für abgeschlossen erklärst, bestätige in Staging unter realistischem Traffic, nicht nur in CI, alles Folgende: Die Anwendung startet im Produktionsmodus mit aktiviertem OPcache, die vollständige Testsuite läuft durch, ohne dass etwas übersprungen wird, bin/console lint:container und bin/console debug:router laufen beide sauber, und das 6.x-Deprecation-Log bleibt leer. Neue 7.4-Deprecations werden auftauchen (sie sind die Vorschau auf Symfony 8), und das ist erwartet. Sie kommen auf das Backlog für das nächste Upgrade, nicht für dieses.

Response-Zeiten spielen hier ebenfalls eine Rolle. Wenn Staging unter derselben Last langsamer ist als die 6.4-Baseline, suche nach einem Bundle, das aktualisiert wurde, aber die Caching- oder Container-Verbesserungen von Symfony 7 nicht korrekt mitgenommen hat, statt anzunehmen, dass die Regression nichts mit dem Upgrade zu tun hat.

Schritt 8: Mit einem tatsächlich getesteten Rollback deployen

Tagge das Pre-Upgrade-Release vor dem Deploy, und bestätige, dass der Rollback-Pfad funktioniert, indem du ihn tatsächlich einmal in Staging ausführst, nicht nur die Schritte dokumentierst. Ein Symfony-Versionssprung lässt sich auf Anwendungsebene meist sicher zurückrollen, da er selten direkt auf das Datenbankschema zugreift, aber prüfe, dass keine Doctrine-Migration im selben Release von Code abhängt, der nur in 7.4 existiert. Wenn doch, deploye die schema-sicheren Migrationen getrennt vom Versionssprung, damit ein Rollback die Datenbank nicht in einem Zustand zurücklässt, den der alte Code nicht lesen kann.

Die Symfony-6.4-auf-7.4-Upgrade-Checkliste

  1. Leere das 6.4-Deprecation-Log unter realistischem Traffic auf null, priorisiere dabei die debug:container --deprecations-Ausgabe.
  2. Führe Rector mit den Symfony- und Doctrine-Attribute-Sets aus und reviewe den Diff nach Rule-Set.
  3. Auditiere jedes Bundle auf Symfony-7-Kompatibilität und löse oder ersetze die, die das Update blockieren.
  4. Bestätige PHP 8.2 oder höher, und upgrade die Laufzeitumgebung bei Bedarf als separates Deploy.
  5. Setze extra.symfony.require auf 7.4.* und führe composer update "symfony/*" --with-all-dependencies aus.
  6. Aktualisiere Flex-Recipes einzeln, reviewe jeden Diff und committe separat.
  7. Smoke-teste Routing und Authentifizierung direkt, da beide nach der Entfernung von Annotation- und Security-System lautlos fehlschlagen können.
  8. Validiere in Staging: sauberer Container, sauberer Router, vollständige Testsuite, leeres 6.x-Deprecation-Log und Response-Zeiten auf oder besser als die 6.4-Baseline.
  9. Tagge das Pre-Upgrade-Release und teste den Rollback-Pfad vor dem Produktions-Deploy.
  10. Protokolliere die neuen 7.4-Deprecations, die auftauchen, als Start-Backlog für Symfony 8.

Hilfe beim Upgrade

Der Schritt, der auf diesem Upgrade die meiste Kalenderzeit frisst, ist fast nie der Versionssprung selbst. Es ist das Bundle-Audit und die Deprecation-Bereinigung, besonders bei einer Anwendung, die ein oder zwei Jahre ohne beides ausgekommen ist. Wenn dein Team abwägt, ob es das intern durchführt oder externe Hilfe holt, zeigt dir ein kurzes Code-Quality-Audit der aktuellen 6.4-Codebasis, wie groß das Deprecation-Backlog tatsächlich ist, bevor du einen Sprint dafür reservierst, und unsere Legacy-Code-Optimierung kann die Rector- und Flex-Abstimmungsdurchgänge neben deinem Team ausführen statt an seiner Stelle. Melde dich unter hello@wolf-tech.io oder besuche wolf-tech.io, wenn du eine zweite Meinung zum Plan willst, bevor du startest.