Zum Inhalt springen

Symfony 7 Upgrade-Guide: Migration von Symfony 6 auf 7

Veröffentlicht am 15. Jan. 2025 | ca. 2 Min. Lesezeit |

Symfony 7 wurde im November 2023 veröffentlicht und markiert einen wichtigen Schnitt in der Symfony-Geschichte: PHP 8.2 ist jetzt Voraussetzung, zahlreiche Deprecations aus Symfony 5 und 6 wurden endgültig entfernt, und das Framework hat an vielen Stellen aufgeräumt. Wer heute noch auf Symfony 6.4 entwickelt, sollte die Migration zu Symfony 7 einplanen — Symfony 6.4 wird bis November 2027 mit Sicherheits-Updates versorgt, aber neue Features gibt es nur noch in Symfony 7.

Dieser Guide beschreibt die Migration anhand eines typischen Symfony-6-Projekts mit Doctrine ORM, API Platform und dem Symfony Security Bundle.

Voraussetzungen

Bevor man beginnt, müssen einige Grundvoraussetzungen erfüllt sein:

  • PHP 8.2 oder höher ist Pflicht. Symfony 7 nutzt aktiv PHP-8.2-Features wie readonly Properties.
  • Das Projekt sollte auf Symfony 6.4 laufen (der letzten 6.x-Version), weil alle Symfony-7-Deprecation-Warnungen dort schon sichtbar sind.
  • PHPStan oder Psalm helfen, versteckte Probleme zu finden.

Zunächst prüft man, ob alle Deprecation-Warnungen aus Symfony 6 bereinigt sind:

ddev exec bin/console debug:container --deprecations

Idealerweise ist diese Liste leer, bevor man den Versions-Sprung wagt.

Schritt 1: composer.json anpassen

Der eigentliche Upgrade beginnt in der composer.json. Man erhöht die Symfony-Versionsbeschränkungen auf ^7.0:

{
    "require": {
        "php": ">=8.2",
        "symfony/framework-bundle": "^7.0",
        "symfony/console": "^7.0",
        "symfony/doctrine-bridge": "^7.0",
        "symfony/security-bundle": "^7.0",
        "symfony/twig-bundle": "^7.0",
        "symfony/validator": "^7.0",
        "symfony/form": "^7.0",
        "symfony/mailer": "^7.0",
        "symfony/messenger": "^7.0"
    }
}

Anschließend führt man das Update durch:

ddev exec composer update "symfony/*" --with-all-dependencies

Composer wird dabei Konflikte melden, wenn andere Pakete noch inkompatible Symfony-Versionsanforderungen haben. Häufige Kandidaten sind ältere Bundles oder API-Platform in einer älteren Hauptversion.

Schritt 2: Entfernte Features und Breaking Changes

Symfony 7 hat alle Klassen, Methoden und Parameter entfernt, die in Symfony 6 als @deprecated markiert waren. Hier die häufigsten Breaking Changes:

AbstractController: getDoctrine() entfernt

In Symfony 6 konnte man noch $this->getDoctrine() im Controller nutzen. Das ist in Symfony 7 weg. Die Lösung: Den EntityManagerInterface oder das ManagerRegistry per Dependency Injection einbinden.

<?php

declare(strict_types=1);

namespace App\Controller;

use App\Repository\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class ProductController extends AbstractController
{
    public function __construct(
        private readonly ProductRepository $productRepository,
    ) {
    }

    #[Route('/products', name: 'product_list')]
    public function list(): Response
    {
        $products = $this->productRepository->findAll();

        return $this->render('product/list.html.twig', [
            'products' => $products,
        ]);
    }
}

Routing-Annotationen: @Route durch #[Route] ersetzt

Das symfony/routing Bundle hat die alten Doctrine-Annotations komplett gestrichen. Wer noch @Route("/path") verwendet, muss auf PHP-Attribute umstellen:

<?php

declare(strict_types=1);

namespace App\Controller;

// Vorher (Symfony 6, deprecated):
// use Symfony\Component\Routing\Annotation\Route;

// Nachher (Symfony 7):
use Symfony\Component\Routing\Attribute\Route;

class MyController
{
    #[Route('/my-path', name: 'my_route', methods: ['GET'])]
    public function index(): Response
    {
        // ...
    }
}

Security: AuthenticationEntryPointInterface geändert

Das Security-Subsystem hat einige Interface-Änderungen erhalten. Wer eigene Authenticators implementiert hat, muss prüfen ob die Methoden-Signaturen noch stimmen.

Serializer: ContextAwareNormalizerInterface entfernt

Eigene Normalizer, die dieses Interface implementiert haben, müssen auf die neue Schreibweise umgestellt werden, bei der der $context-Parameter direkt in normalize() und supportsNormalization() übergeben wird.

Schritt 3: PHP-8.2-Features nutzen

Symfony 7 setzt PHP 8.2 voraus — das ist eine gute Gelegenheit, den eigenen Code zu modernisieren.

Readonly Properties und Readonly Classes

Readonly Properties wurden in PHP 8.1 eingeführt. PHP 8.2 erweitert dieses Konzept um Readonly Classes — damit werden alle Properties einer Klasse automatisch als readonly markiert:

<?php

declare(strict_types=1);

namespace App\Model;

final class Money
{
    public function __construct(
        public readonly int $amount,
        public readonly string $currency,
    ) {
    }

    public function add(Money $other): self
    {
        if ($this->currency !== $other->currency) {
            throw new \InvalidArgumentException('Currency mismatch');
        }

        return new self($this->amount + $other->amount, $this->currency);
    }
}

Fibers für asynchrone Verarbeitung

PHP 8.1 führte Fibers ein — eine vollständige Implementierung für kooperatives Multitasking auf niedrigem Level. Fibers ermöglichen es, Funktionen an beliebigen Stellen zu unterbrechen und später fortzusetzen, was besonders für asynchrone I/O-Operationen nützlich ist. Frameworks wie ReactPHP und AMPHP nutzen Fibers als Grundlage für ihre Event-Loops.

Schritt 4: Symfony-Flex-Recipes aktualisieren

Symfony Flex verwaltet Konfigurationsdateien. Nach dem Versions-Upgrade prüft man, ob neue Recipes verfügbar sind:

ddev exec composer recipes:update

Das zeigt, welche Bundles neue Konfigurationsvorlagen bereitstellen. Nicht jede Änderung muss übernommen werden, aber es lohnt sich, die Diffs zu prüfen.

Schritt 5: Deprecation-Warnungen in Symfony 7 beheben

Nach dem Upgrade auf Symfony 7 tauchen neue Deprecations auf — das sind Hinweise auf Features, die in Symfony 8 entfernt werden. Den Überblick behält man mit dem Profiler im dev-Modus oder mit PHPStan und dem Symfony-PHPStan-Plugin:

ddev exec composer require --dev phpstan/phpstan phpstan/phpstan-symfony
ddev exec vendor/bin/phpstan analyse src

Rector: Upgrade automatisieren

Viele der beschriebenen Änderungen lassen sich mit Rector automatisieren. Rector ist ein PHP-Refactoring-Tool, das den Abstract Syntax Tree (AST) analysiert und Code-Transformationen regelbasiert durchführt.

Installation

ddev exec composer require rector/rector --dev

Das Paket rector/rector-symfony ist bereits enthalten und muss nicht separat installiert werden.

Konfiguration

Rector erkennt die installierte Symfony-Version automatisch aus vendor/composer/installed.json und wendet die passenden Regeln an. Eine rector.php im Projektroot:

<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withPaths([
        __DIR__ . '/src',
        __DIR__ . '/tests',
    ])
    ->withSkip([
        __DIR__ . '/src/Kernel.php',
    ])
    ->withComposerBased(
        symfony: true,
        doctrine: true,
        phpunit: true,
    )
    ->withAttributesSets(
        symfony: true,
        doctrine: true,
    )
    ->withPreparedSets(
        symfonyCodeQuality: true,
        symfonyConstructorInjection: true,
    );

Was Rector automatisch erledigt

Rector kennt die konkreten Symfony-Deprecations und kann viele davon automatisch umschreiben:

  • getDoctrine() entfernen und durch Constructor Injection ersetzen
  • @Route-Annotations zu #[Route]-Attributes konvertieren
  • $defaultName/$defaultDescription in Console Commands durch #[AsCommand] ersetzen
  • $form->createView() in Render-Aufrufen vereinfachen
  • Return-Type-Deklarationen ergänzen, die Symfony 7 voraussetzt
  • Umbenannte Klassen automatisch aktualisieren (z.B. RequestMatcher zu ChainRequestMatcher)

Workflow

# 1. Dry-Run: Zeigt geplante Änderungen ohne sie auszuführen
ddev exec vendor/bin/rector process --dry-run

# 2. Änderungen anwenden
ddev exec vendor/bin/rector process

# 3. Code-Style korrigieren (Rector arbeitet auf AST-Ebene, Formatierung kann leiden)
ddev exec vendor/bin/php-cs-fixer fix

# 4. Tests laufen lassen
ddev exec vendor/bin/phpunit

Was Rector nicht kann

Rector arbeitet nur mit PHP-Code. Folgendes muss manuell erledigt werden:

  • YAML/XML-Konfiguration: Änderungen an services.yaml, routes.yaml, security.yaml
  • Twig-Templates: Umbenannte Filter, geänderte Funktionen
  • Drittanbieter-Bundles: Inkompatibilitäten mit weniger verbreiteten Bundles
  • Verhaltensänderungen: Wenn sich die Semantik einer Methode geändert hat (z.B. Exceptions statt false)
  • Komplexe Interface-Änderungen: Wenn eigene Implementierungen neue Signaturen erfordern

Rector deckt erfahrungsgemäß 60–80% der mechanischen Upgrade-Arbeit ab. Die verbleibenden Anpassungen sind dann die, die tatsächlich menschliches Urteilsvermögen erfordern.

Häufige Stolpersteine

Drittanbieter-Bundles: Nicht alle Bundles sind sofort mit Symfony 7 kompatibel. Besonders ältere Bundles ohne aktive Wartung können Probleme machen. Hier hilft composer outdated und ein Blick in die GitHub-Issues des jeweiligen Bundles.

API Platform 3.x: API Platform 2.x ist nicht mit Symfony 7 kompatibel. Wer API Platform verwendet, muss zunächst auf Version 3.x migrieren.

Doctrine-Bundles: doctrine/doctrine-bundle 2.x ist mit Symfony 7 kompatibel, aber man sollte die Release Notes prüfen.

Fazit

Die Migration von Symfony 6 auf 7 ist überschaubar, wenn man das Projekt bereits auf dem Stand von Symfony 6.4 hält und alle Deprecation-Warnungen behoben hat. Die größten Aufwände entstehen durch entfernte Legacy-APIs wie getDoctrine() und alte Annotation-Routen. Wer den Umstieg strukturiert angeht — PHP 8.2, Deprecations bereinigen, Drittanbieter prüfen — hat in der Regel in einem Tag ein kleineres Projekt migriert.

Für größere Projekte empfiehlt sich ein Feature-Branch mit automatisierten Tests als Sicherheitsnetz: ddev exec vendor/bin/phpunit sollte nach dem Upgrade grün bleiben.

Thomas Wunner

Thomas Wunner

Fachinformatiker für Anwendungsentwicklung mit Ausbildereignungsprüfung und über 14 Jahre Erfahrung im Aufbau skalierbarer Webanwendungen mit Symfony und Shopware. Abseits der Tastatur ist Thomas als Rettungsschwimmer in der Wasserwacht aktiv, legt als DJ auf und erkundet die Umgebung auf dem Motorrad.

Kommentare

Kommentare werden von Remark42 bereitgestellt. Beim Laden werden Daten an unseren Kommentar-Server übertragen.