KwikTech Symfony · API Platform · Doctrine upgrades
Upgrade-Leitfaden · 14 Min. Lesezeit

Upgrade von Symfony 5.4 auf 6.4: der vollständige Leitfaden

Schritt für Schritt: PHP 8.1, Deprecations auf null, das neue Security-System statt Guard, Swiftmailer zu Mailer, Annotations zu Attributes und das composer update auf 6.4 selbst.

Veröffentlicht 2026-09-12. Geschrieben für Teams, die das Upgrade selbst machen; wer es lieber machen lässt, findet hier Upgrades zum Festpreis. Auch verfügbar auf English.

Warum 5.4 auf 6.4 der große Schritt ist

Symfony 6.0 hat alles entfernt, was 5.4 als deprecated markiert hatte, und 5.x hat viel markiert: das Guard-Security-System, den alten session-Service, die Swiftmailer-Integration, den Großteil dessen, was SensioFrameworkExtraBundle tat, und annotationsbasierte Konfiguration zugunsten von PHP-8-Attributes. 6.4 ist das letzte 6.x-Release und das einzige, das noch Fixes erhält; das Ziel ist also immer 6.4, nie 6.0. Bugfixes für 6.4 enden im November 2026, weshalb die meisten Teams danach direkt auf 7.4 weitergehen.

Die gute Nachricht: Alles unten lässt sich auf 5.4 erledigen, in getrennten Pull Requests, jeder für sich deploybar. Der eigentliche Versionssprung ist der letzte und kleinste Schritt.

Schritt 0: Voraussetzungen

  • PHP 8.1 oder neuer. Symfony 6.4 setzt 8.1 voraus. Machen Sie das zuerst, auf 5.4, mit Ihren aktuellen Abhängigkeiten. 5.4 läuft ohne Änderungen auf PHP 8.1 bis 8.3.
  • Symfony 5.4.x im neuesten Patch, mit symfony/flex 2.x und Composer 2.
  • Eine Testsuite, die läuft. Auch eine dünne. Fügen Sie symfony/phpunit-bridge hinzu, falls sie fehlt; Sie brauchen ihren Deprecation-Report.
  • Rector als Dev-Abhängigkeit: composer require --dev rector/rector.

Schritt 1: den Deprecation-Report lesbar machen

Die PHPUnit-Bridge gibt jede Deprecation aus, die während des Testlaufs ausgelöst wird, gruppiert nach Meldung. Konfigurieren Sie sie so, dass Deprecations aus eigenem Code den Lauf fehlschlagen lassen, damit die Zahl nur noch sinken kann:

# phpunit.xml.dist
<php>
    <env name="SYMFONY_DEPRECATIONS_HELPER" value="max[self]=0&amp;max[direct]=0"/>
</php>

Führen Sie die Suite aus und lesen Sie den Abschnitt "Remaining direct deprecation notices". Diese Liste ist die Arbeit. Typische Einträge in einer 5.4-Codebasis:

  • "Since symfony/security-guard 5.3: The Guard component is deprecated" → Schritt 3.
  • "The 'session' service is deprecated, use RequestStack" → RequestStack injizieren und getSession() aufrufen.
  • "Method X() will return Y in 6.0, not returning Y is deprecated" → Schritt 5.
  • Annotationsbasiertes Routing, SensioFrameworkExtra-Annotations → Schritt 4.

Schritt 2: die Rector-Sets 5.4 und 6.x auf 5.4 ausführen

Rector erledigt den mechanischen Teil: umbenannte Klassen, neue Signaturen, getSession(), umbenannte Form-Optionen, Event-Namen. Beginnen Sie mit den Sets bis 6.4, während Sie noch 5.4 ausführen; der resultierende Code ist mit beiden kompatibel.

// rector.php
use Rector\Config\RectorConfig;
use Rector\Symfony\Set\SymfonySetList;

return RectorConfig::configure()
    ->withPaths([__DIR__.'/src', __DIR__.'/tests'])
    ->withSets([
        SymfonySetList::SYMFONY_54,
        SymfonySetList::SYMFONY_60,
        SymfonySetList::SYMFONY_61,
        SymfonySetList::SYMFONY_62,
        SymfonySetList::SYMFONY_63,
        SymfonySetList::SYMFONY_64,
        SymfonySetList::SYMFONY_CODE_QUALITY,
    ])
    ->withSymfonyContainerXml(__DIR__.'/var/cache/dev/App_KernelDevDebugContainer.xml');
vendor/bin/rector process --dry-run   # prüfen
vendor/bin/rector process             # anwenden, dann Tests ausführen

Committen Sie das als eigenen PR. Mischen Sie es nicht mit manuellen Änderungen; ein Rector-Diff zu reviewen ist eine andere Tätigkeit als ein Refactoring zu reviewen.

Schritt 3: Guard durch das neue Authenticator-System ersetzen

Symfony 5.4 liefert das Authenticator-basierte Security-System als Opt-in mit. Schalten Sie es ein und schreiben Sie jeden Guard-Authenticator auf 5.4 um, sodass die Security beim Sprung auf 6.4 bereits erledigt ist.

# config/packages/security.yaml
security:
    enable_authenticator_manager: true   # nur 5.4; der Schlüssel verschwindet in 6.0
    firewalls:
        main:
            lazy: true
            custom_authenticator: App\Security\ApiKeyAuthenticator
            # 'anonymous: true' ist weg; 'lazy: true' ersetzt es
// Vorher: Guard
class ApiKeyAuthenticator extends AbstractGuardAuthenticator
{
    public function getCredentials(Request $request) { return $request->headers->get('X-API-KEY'); }
    public function getUser($credentials, UserProviderInterface $userProvider) { /* ... */ }
    public function checkCredentials($credentials, UserInterface $user) { return true; }
    // supports(), onAuthenticationSuccess(), onAuthenticationFailure(), start(), supportsRememberMe()
}

// Nachher: Authenticator + Passport
class ApiKeyAuthenticator extends AbstractAuthenticator
{
    public function supports(Request $request): ?bool
    {
        return $request->headers->has('X-API-KEY');
    }

    public function authenticate(Request $request): Passport
    {
        $key = (string) $request->headers->get('X-API-KEY');

        return new SelfValidatingPassport(new UserBadge($key, fn (string $key) => $this->users->findByApiKey($key)));
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        return null; // Request weiterlaufen lassen
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $e): ?Response
    {
        return new JsonResponse(['error' => $e->getMessageKey()], 401);
    }
}

Formular-Logins brauchen meist gar keine eigene Klasse mehr: form_login: in der Firewall plus make:security:form-login deckt es ab. Passwörter: encoders: wurde zu password_hashers:, und UserPasswordEncoderInterface zu UserPasswordHasherInterface (Rector benennt beides um).

Schritt 4: Annotations zu Attributes, SensioFrameworkExtra raus

SensioFrameworkExtraBundle ist verwaist und lässt sich auf Symfony 7 nicht installieren. Seine Funktionen leben jetzt im Core. Auf 5.4 mit PHP 8.1 können Sie das Routing sofort auf Attributes umstellen:

# config/routes/annotations.yaml (Name unter 5.4) → ab 6.1 heißt der Typ 'attribute'
controllers:
    resource: ../../src/Controller/
    type: annotation      # funktioniert in 6.4 weiter; in 'attribute' umbenennen, wenn Sie dort sind
// Vorher
/**
 * @Route("/orders/{id}", name="order_show", methods={"GET"})
 * @ParamConverter("order", class="App\Entity\Order")
 * @IsGranted("ROLE_SALES")
 * @Template()
 */
public function show(Order $order) { return ['order' => $order]; }

// Nachher (Symfony 6.2+ Attributes; ParamConverter → MapEntity, Template → explizites render)
#[Route('/orders/{id}', name: 'order_show', methods: ['GET'])]
#[IsGranted('ROLE_SALES')]
public function show(#[MapEntity] Order $order): Response
{
    return $this->render('order/show.html.twig', ['order' => $order]);
}

Rectors SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES und DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES übernehmen Routen, Doctrine-Mapping, Validierungs-Constraints und Serializer-Gruppen. #[MapEntity] und #[IsGranted] gibt es ab 6.2; wenn Sie noch eine Weile auf 5.4 bleiben müssen, behalten Sie SensioFrameworkExtra 6.2 (das an die Core-Attributes weiterleitet) und entfernen es direkt nach dem Sprung. Details pro Bundle stehen im Leitfaden zu verwaisten Bundles.

Schritt 5: Return Types

Symfony 6 hat native Return Types zu den meisten Interfaces und abstrakten Methoden hinzugefügt. Ihre Klassen, die sie implementieren (EventSubscriberInterface::getSubscribedEvents(), AbstractType::configureOptions(), Voter, Normalizer, Twig-Extensions), müssen denselben Return Type deklarieren, sonst bricht PHP mit einem Fatal Error ab. Symfony liefert ein Tool mit, das sie gesammelt patcht:

SYMFONY_PATCH_TYPE_DECLARATIONS='force=2&php=8.1' vendor/bin/patch-type-declarations

Auf 5.4 ausführen, Diff prüfen, Tests laufen lassen, committen. Das ist die Änderung, die Teams erwischt, die die Deprecation-Jagd überspringen: Der Fatal Error erscheint erst nach dem Sprung.

Schritt 6: Swiftmailer zu Mailer

Wenn symfony/swiftmailer-bundle in Ihrer composer.lock steht, ersetzen Sie es auf 5.4. Die Mailer-Komponente ist seit 4.4 stabil.

// Vorher
$message = (new \Swift_Message('Welcome'))->setFrom('noreply@example.com')->setTo($user->getEmail())->setBody($html, 'text/html');
$this->mailer->send($message);

// Nachher
$email = (new TemplatedEmail())
    ->from('noreply@example.com')
    ->to($user->getEmail())
    ->subject('Welcome')
    ->htmlTemplate('emails/welcome.html.twig')
    ->context(['user' => $user]);
$this->mailer->send($email); // MailerInterface

Transports wandern in MAILER_DSN. Der Spool wird zu Messenger: SendEmailMessage auf einen asynchronen Transport routen, und das Verhalten ist dasselbe.

Schritt 7: der Sprung selbst

Mit Deprecations auf null, Security auf Authenticators, Attributes an Ort und Stelle und gepatchten Return Types ist der Versionswechsel eine Composer-Übung:

composer config extra.symfony.require "6.4.*"
composer update "symfony/*" --with-all-dependencies
composer recipes:update     # führt Sie durch geänderte Flex-Recipes, eines nach dem anderen

Rechnen Sie mit diesen Nacharbeiten:

  • enable_authenticator_manager muss aus der security.yaml entfernt werden (unbekannter Schlüssel in 6.x).
  • Routing: type: annotation → type: attribute, dann doctrine/annotations entfernen, wenn nichts anderes es braucht.
  • Bundles, die bei ^5.4 gedeckelt waren, brauchen ihre nächste Major-Version. Die Kompatibilitätsseiten zeigen, welches Release jedes Bundles 6.4 zuerst erlaubt hat.
  • symfony/security-guard, symfony/swiftmailer-bundle, sensio/framework-extra-bundle fliegen aus der composer.json.

Tests ausführen, bin/console lint:container, lint:twig templates, lint:yaml config und doctrine:schema:validate laufen lassen. Dann 6.4 deployen, ohne etwas anderes im Release.

Reihenfolge der Pull Requests

  1. PHP 8.1 (oder 8.2) auf 5.4
  2. Rector-Sets 5.4–6.4
  3. Guard → Authenticators (Opt-in auf 5.4)
  4. Annotations → Attributes, SensioFrameworkExtra ersetzt
  5. Return Types gepatcht
  6. Swiftmailer → Mailer
  7. Deprecations auf null (was der Report noch listet)
  8. Composer-Sprung auf 6.4, Recipes, Bundle-Majors

Wenn API Platform 2.x im Projekt ist, schieben Sie die Migration 2.6 → 2.7 vor den Sprung: API Platform 3 braucht Symfony 6.4, aber 2.7 läuft auf beiden und lässt Sie die Provider-/Processor-Arbeit noch auf 5.4 erledigen. Doctrine ORM 3 kann davor oder danach kommen; siehe den ORM-Leitfaden.

Kein Raten mehr, welche Bundles 6.4 blockieren

Der Scan listet jedes Paket in Ihrer composer.lock, dessen installiertes Release Symfony unterhalb von 6.4 deckelt, ob ein neueres Release die Grenze aufhebt, und die gefundenen Cliffs (Guard, Annotations, Swiftmailer, API Platform 2).

Mein Projekt scannen Assessment anfragen (€750)

Weitere Leitfäden