Symfony 7 Upgrade Guide: What Changed and How to Migrate

#Symfony 7 upgrade

Founder & Lead Developer

Expert in software development and legacy code optimization

LinkedIn

Short answer: To migrate from Symfony 6 to 7, upgrade to Symfony 6.4 first, fix every deprecation it reports, then change the Composer constraints to Symfony 7.4 (the long-term support release) and sync your Flex recipes. Symfony 7 adds no new programming model; it removes what 6.4 deprecated and requires PHP 8.2. Symfony 6.4 stops receiving bug fixes in November 2026, so a team still on 6.4 should plan the move now.

Open any Symfony 6.4 application that has been running in production for a while and run bin/console debug:container --deprecations. The output is usually uncomfortable: dozens of deprecation notices pointing at long-forgotten bundles, legacy service definitions, and APIs the framework has been warning about for two release cycles. That output is the preview of your Symfony 7 upgrade. Every deprecation in 6.4 is a hard break in 7.0.

Symfony 7 is not a disruptive rewrite. It is a disciplined cleanup: removing everything that was deprecated in the 6.x line, tightening the type system, simplifying the dependency injection container, and refining a few components that had accumulated inconsistencies. For teams that have kept their 6.4 codebase clean, the upgrade takes a few days. For teams that have been deferring deprecation fixes, it is a focused multi-week engineering project. This guide covers what changed, which 7.x version to target, how to prepare, and the practical migration steps that keep risk under control.

What Actually Changed in Symfony 7

The headline of the Symfony 7 upgrade is simple: every public API deprecated in 6.x has been removed. There are no new syntax surprises, no radical departures from the Symfony programming model, and no forced move to a different component architecture. If your 6.4 application runs without triggering deprecation warnings, it will almost certainly run on 7.x with minimal changes.

The substantive changes fall into four categories.

First, the type system is stricter. Many component methods gained native parameter and return types. Return types on your overrides must be compatible with the parent, which PHP enforces at class load time. Custom voters, event subscribers, normalizers, and form type extensions often need their method signatures updated to match the new parent class contracts.

Second, the dependency injection container is leaner. Several legacy service IDs and aliases have been removed, and ContainerAwareInterface and ContainerAwareTrait are gone. Code that pulled services out of the container at runtime has to switch to constructor injection or a service locator. The container compilation is less forgiving of sloppy service wiring.

Third, Security saw the largest component cleanup. The old authentication system is fully gone, including every transition helper that bridged old and new authenticators. Anything still built on the pre-5.3 security model has to be rewritten on top of the authenticator system before the upgrade can happen.

Fourth, the PHP floor moved. Every Symfony 7 release, from 7.0 to 7.4, requires PHP 8.2 or higher. Running on an older PHP runtime is not an option: the language features Symfony 7 relies on cannot be polyfilled. If you are also on an old PHP version, do the PHP upgrade as a separate step before the framework upgrade, so that each change can be rolled back on its own.

The official UPGRADE-7.0.md in the Symfony repository lists every BC break by component. Reading it end-to-end before starting is the single most valuable hour you can spend on this project.

Which Symfony 7 Version to Target: 7.4 LTS

When this guide was first written, the question was "7.0 or wait for a later minor". As of September 2026 the answer is settled: target Symfony 7.4. It was released in November 2025 together with Symfony 8.0, it is the long-term support release of the 7.x line, and it receives bug fixes until November 2028 and security fixes until November 2029. The minor releases in between (7.0 to 7.3) are all out of support, so there is no reason to stop at one of them.

Going from 6.4 straight to 7.4 does not mean going through each minor one by one. Symfony minor releases are backward compatible, so once your code is deprecation-free on 6.4, you can set the constraint to 7.4 directly. What you do get is a new round of deprecations: 7.4 deprecates things that 8.0 removes, and those show up in your log after the bump. They do not block the upgrade; they are the backlog for the next one.

For a deeper look at how the support windows line up for different project types, see our overview of Symfony LTS versions and how long each one is safe.

What Symfony 7.1 to 7.3 Added Along the Way

A 6.4 to 7.4 migration brings in two years of features at once. You do not have to adopt them during the upgrade, but a few change how new code is written, and reviewers should know about them:

  • Symfony 7.3 (May 2025) added the ObjectMapper component for mapping DTOs to entities with #[Map] attributes, the JsonPath component for querying JSON documents, invokable console commands that declare arguments and options with #[Argument] and #[Option] attributes, and security voters that can explain their decisions in the profiler. The last one helps a lot when access rules change during the upgrade and a test starts returning 403.
  • Across 7.1 to 7.3, more framework features moved to attributes, so controller and service code can drop configuration that used to live in YAML.

What Symfony 7.4 Changes for Your Configuration

Symfony 7.4 contains the same features as 8.0, plus the deprecation layer. Several of its changes matter during a migration:

  • XML configuration is deprecated. If your bundles or services are configured in XML, plan to move them to YAML or PHP. It still works in 7.4, but it will not in 8.0.
  • A new array-based PHP configuration format replaces the fluent PHP config builders, with generated array shapes that give autocompletion and static analysis for config files.
  • Native FrankenPHP worker mode support. The separate runtime/frankenphp-symfony package is no longer needed. If you are considering a runtime change as well, our guide to running FrankenPHP in production covers the worker-mode caveats.
  • Controller helpers can be used without extending AbstractController, and routing, security, and event attributes gained options that make more YAML configuration unnecessary.

Preparing Your Codebase: Symfony 6 to 7 Groundwork

The Symfony 6 to 7 migration is dramatically easier if you start from a clean 6.4 LTS codebase with zero deprecation warnings. Symfony 6.4 is the designated upgrade bridge: all 7.0 removals are available as deprecation warnings in 6.4, which means you can fix them while staying on the LTS release and only flip the Composer constraint once the warnings are gone.

The practical sequence is: upgrade to 6.4 first if you are not there yet, enable deprecation logging in dev and staging, triage the warnings, fix them in the 6.4 branch, then perform the actual 7.4 version bump as a nearly empty Composer change. Teams still on Symfony 5.4 follow the same pattern with one extra hop; our write-up on migrating from Symfony 5 to 7 without a code freeze covers that longer path.

Route deprecations to their own log file in config/packages/monolog.yaml:

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

Run your full test suite, your CI integration tests, and if possible a staging environment under production-like traffic for a day or two. The deprecation log is your migration backlog. Entries with stack traces pointing into vendor code usually resolve when you update a bundle. Entries pointing into your own code are the ones that require work.

If that backlog runs into the hundreds, or the deprecations sit in code nobody on the team wants to touch, the upgrade has turned into a refactoring project. Plan it as one, with its own scope and tests, or bring in outside help for the Symfony refactoring so feature work does not stall behind it.

A particularly useful command for triaging the backlog:

bin/console debug:container --deprecations

This lists only deprecations related to service configuration and is often where the biggest wins live: removing a legacy service alias or updating a bundle configuration can silence dozens of individual warnings at once.

Common Symfony Deprecations to Address First

A few Symfony deprecations appear in almost every 6.x application and are worth prioritizing because they unblock the rest of the upgrade.

The old Security class deprecations. Any controller or service still type-hinting Symfony\Component\Security\Core\Security needs to switch to Symfony\Bundle\SecurityBundle\Security (different namespace, same interface for common methods). The getUser(), isGranted(), and getToken() methods are unchanged; only the import line changes.

Legacy authenticator interfaces. If your codebase still implements authenticators from the old security system, migrate to the AbstractAuthenticator base class. This is a larger change because the method signatures differ, but it is required: the old system is removed in 7.0.

Annotations to attributes. @Route, @Security, @ParamConverter, and similar annotations have been deprecated in favor of PHP 8 attributes (#[Route], #[IsGranted], #[MapEntity]). Doctrine mapping follows the same pattern. Rector handles most of this conversion automatically.

Container-aware code. Controllers or services that implement ContainerAwareInterface or call $this->container->get() on the full container need constructor injection or a service subscriber instead.

The assertResponseIsSuccessful() and related test assertions have stricter signatures. Browser kit tests that passed loosely typed expectations will fail until they are updated.

Flex Recipes: Configuration Drift and Recipe Updates

Symfony Flex recipes have evolved significantly between 6.0 and 7.4. Many applications have diverged from the recipe-provided configuration over time (hand edits to framework.yaml, security.yaml, routes.yaml) and this drift is where upgrade friction appears.

After updating the Symfony version constraints in composer.json, list the recipes that have newer versions and update them one at a time:

composer recipes
composer recipes:update symfony/framework-bundle

recipes:update applies the new recipe version as a patch against your current files and leaves conflicts for you to resolve, like a merge. Never accept the changes blindly. Review each diff, reconcile your customizations with the new recipe defaults, and commit the results as small, reviewable changes, one recipe per commit.

The Flex recipes most likely to require reconciliation are:

config/packages/framework.yaml: session handler configuration changed, default CSRF settings shifted, and the http_method_override default flipped.

config/packages/security.yaml: the access_control and firewall syntax got stricter; anonymous user handling was removed entirely.

config/packages/doctrine.yaml: ORM configuration defaults changed, and the auto_mapping behavior for bundles is more conservative.

.env defaults: several environment variables were renamed or consolidated.

For any recipe you override heavily (a common pattern for security.yaml), consider moving your customizations into a separate file that the recipe does not manage (for example, config/packages/security_custom.yaml) and keeping the recipe-managed file as close to upstream as possible. This pays dividends on every future upgrade.

Tooling: Rector, PHPStan, and the Symfony Upgrade Path

A Symfony 7 upgrade without automated tooling is an order of magnitude more painful than one with it. Three tools carry most of the load.

Rector with the Symfony rules automates the mechanical refactorings: annotation-to-attribute conversion, deprecated method renames, interface migrations, and type signature updates. Current Rector versions can pick the right Symfony and Doctrine rule sets from the versions in your composer.json, so you no longer need to list each upgrade set by hand:

// 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);

Run it against a test branch, review the diff in small chunks, and commit by rule set so the git history documents what changed and why. Our guide on Rector for legacy PHP goes deeper into running it safely on a large codebase.

PHPStan with the Symfony extension (phpstan/phpstan-symfony) catches container and service errors that Rector cannot. Running at level 6 or higher before the upgrade and keeping the same level after is the cleanest validation that service wiring still works.

composer audit and bin/console lint:container validate dependencies and configuration at the framework level and should be part of the CI pipeline for the upgrade branch.

Running the Upgrade and Validating

The actual composer update to 7.4 is anticlimactic if the groundwork is done. Change every symfony/* constraint in composer.json from 6.4.* to 7.4.*, and set the Flex constraint so Flex only installs matching packages:

"extra": {
    "symfony": {
        "require": "7.4.*"
    }
}

Then run the update and the checks:

# 1. Resolve the full dependency graph
composer update "symfony/*" --with-all-dependencies

# 2. Update the recipes that changed (see above)
composer recipes

# 3. Rebuild cache and run linters
bin/console cache:clear
bin/console lint:container
bin/console debug:router

# 4. Run the test suite
bin/phpunit

The step that causes the most trouble is composer update --with-all-dependencies: third-party bundles without 7.x support will block the update. The fix is either upgrading to a newer bundle version, switching to an alternative, or temporarily forking the bundle and adjusting its constraints. By now most maintained bundles support 7.4, so a bundle that still blocks the update is often an unmaintained one, and replacing it is usually the better fix.

On the validation side, a few checks should pass before considering the upgrade complete. The application boots in prod mode with OPcache enabled. The full test suite passes without skipped tests. No 6.x deprecation warnings remain in the log under realistic traffic (new 7.4 deprecations are expected and go on the backlog for Symfony 8). Critical user journeys work in a staging environment that mirrors production configuration. Response times are at least as good as on 6.4; if they got worse, look for a misconfigured service or a bundle that did not upgrade cleanly.

Symfony 6 to 7 Migration Checklist

For teams that want the whole path on one screen:

  1. Upgrade PHP to 8.2 or newer (8.4 if you want Symfony 8 later without another PHP bump), as its own deploy.
  2. Get to Symfony 6.4 and update all bundles to their latest 6.4-compatible versions.
  3. Log deprecations in dev, CI, and staging; fix them until the log stays empty under realistic traffic.
  4. Run Rector and PHPStan on the 6.4 branch and merge their changes in small pull requests.
  5. Change the constraints to 7.4.*, set extra.symfony.require, and run composer update "symfony/*" --with-all-dependencies.
  6. Update Flex recipes one at a time with composer recipes:update.
  7. Run the full test suite, then run staging under production-like load before the production deploy.
  8. Collect the new 7.4 deprecations as the starting backlog for Symfony 8.

After 7.4: Planning the Move to Symfony 8

Symfony 8.0 has the same features as 7.4 without the deprecation layer, and it requires PHP 8.4. Symfony 8.1 followed in May 2026. Non-LTS releases are supported for eight months, so running on 8.x means upgrading to each new minor release as it comes out. Many product teams will prefer to stay on 7.4 LTS and move to the next LTS release, 8.4, when it arrives. Teams with a strong CI pipeline and a habit of small upgrades can follow the 8.x minors instead.

Either way, the mechanics are the same as above: get the deprecation log for 7.4 to zero, then change the constraint. The work you do now to keep 7.4 deprecation-free is the Symfony 8 migration, done in advance.

A Pragmatic Path Forward

For most Berlin and EU engineering teams running Symfony, the upgrade to 7.4 is now due rather than optional. Symfony 6.4 LTS receives bug fixes until November 2026 and security fixes until November 2027. That still leaves time to plan, but not enough to push the migration back by another year.

The teams that upgrade successfully share a pattern: they treat Symfony 6.4 deprecation zero as a first-class engineering goal, invest in Rector and PHPStan before starting the version bump, and split the Flex recipe reconciliation into small reviewable PRs. The teams that struggle are usually the ones that tried to do the whole upgrade in a single branch while also shipping features.

If your team is planning a Symfony 7 upgrade (or sitting on a Symfony 4 or 5 codebase and wondering what the realistic path to modern Symfony looks like), this is exactly the kind of work Wolf-Tech helps with. We have run these upgrades for Symfony applications across Germany and the EU, and the combination of a code quality audit and staged legacy code optimization work tends to turn a scary multi-month project into a predictable engineering program. Contact us at hello@wolf-tech.io or visit wolf-tech.io for a free consultation on your upgrade path.