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/flex2.x und Composer 2. - Eine Testsuite, die läuft. Auch eine dünne. Fügen Sie
symfony/phpunit-bridgehinzu, 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&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" →
RequestStackinjizieren undgetSession()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_managermuss aus der security.yaml entfernt werden (unbekannter Schlüssel in 6.x).- Routing:
type: annotation→type: attribute, danndoctrine/annotationsentfernen, wenn nichts anderes es braucht. - Bundles, die bei
^5.4gedeckelt 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-bundlefliegen 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
- PHP 8.1 (oder 8.2) auf 5.4
- Rector-Sets 5.4–6.4
- Guard → Authenticators (Opt-in auf 5.4)
- Annotations → Attributes, SensioFrameworkExtra ersetzt
- Return Types gepatcht
- Swiftmailer → Mailer
- Deprecations auf null (was der Report noch listet)
- 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).
Weitere Leitfäden
- Upgrade von Symfony 6.4 auf 7.4: Deprecations, Return Types und der Sprung
- API Platform 2.6 auf 3: Resources, Data Provider und Persister migrieren
- API Platform 3 auf 4: der State-basierte Ablauf, RFC-7807-Fehler und Hydra-Änderungen
- Upgrade von Doctrine ORM 2 auf 3 (und DBAL 3 auf 4) in einer Symfony-Anwendung
- Verwaiste Symfony-Bundles ersetzen: SensioFrameworkExtra, Guard, Swiftmailer, FOSRest, FOSUser