Symfony 7 Upgrade Guide: What Changed and How to Migrate

#Symfony 7 upgrade
Sandor Farkas - Founder & Lead Developer at Wolf-Tech

Sandor Farkas

Founder & Lead Developer

Expert in software development and legacy code optimization

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, 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 that accepted mixed or implicitly typed parameters now require explicit scalar or enum types. Return types on internal overrides must match exactly—PHP's LSP checks are enforced more aggressively. Custom voters, event subscribers, 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. Configuration that relied on autowiring by class name through non-standard aliases needs to be explicit. The container compilation is faster but less forgiving of sloppy service wiring.

Third, Security and HttpFoundation saw the largest component cleanup. The old authentication system removed in 5.4 is fully gone, including every transition helper introduced to bridge old and new authenticators. The HttpFoundation Request::create() signature changed, and several session handlers were removed in favor of the cache-based alternatives.

Fourth, some bundles have new minimum PHP requirements. Symfony 7.0 requires PHP 8.2 minimum. Symfony 7.1 recommends PHP 8.3. Running on an older PHP runtime is not an option—the language-level features that Symfony 7 uses (readonly classes, first-class callable syntax, enum types in signatures) cannot be polyfilled.

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.

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 deprecations are available as warnings in 6.4, which means you can fix them while staying on the LTS release and only flip the composer version 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.x version bump as a nearly empty composer change.

Enable deprecation logging in services.yaml:

services:
    Symfony\Component\ErrorHandler\ErrorHandler:
        arguments:
            $logger: '@logger'

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.

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 AuthenticatorInterface from the old security system, migrate to the 5.4+ AbstractAuthenticator base class. This is a larger change because the method signatures differ, but it is required—the old interface 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 annotations for entities follow the same pattern—attributes are the mandatory form in 7.0. A bulk rector rule can handle 90% of this conversion automatically.

Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository constructor signature. The old signature is gone. Repositories must call the new parent constructor with the registry and entity class name explicitly.

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.x. 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, run:

composer symfony:sync-recipes --force --verbose

This applies the latest recipe versions. Flex shows a diff for each changed file and prompts before overwriting. The key word is prompts—never accept the changes blindly. Review each diff, reconcile your customizations with the new recipe defaults, and commit the results as small, reviewable changes.

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 rule set automates the mechanical refactorings: annotation-to-attribute conversion, deprecated method renames, interface migrations, and type signature updates. A typical Rector configuration for this upgrade:

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

return static function (RectorConfig $rectorConfig): void {
    $rectorConfig->paths([
        __DIR__ . '/src',
        __DIR__ . '/tests',
    ]);

    $rectorConfig->sets([
        LevelSetList::UP_TO_PHP_82,
        SymfonyLevelSetList::UP_TO_SYMFONY_70,
        SymfonySetList::SYMFONY_CODE_QUALITY,
        SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES,
    ]);
};

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.

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.

Symfony's own symfony check:security and the symfony console lint:container commands validate 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.x is anticlimactic if the groundwork is done. A typical sequence:

# 1. Update Symfony version constraint
composer require 'symfony/*:^7.0' --no-update

# 2. Update bundles that require a matching major version bump
composer require 'doctrine/doctrine-bundle:^2.11' --no-update
composer require 'twig/twig:^3.8' --no-update

# 3. Resolve the full dependency graph
composer update --with-all-dependencies

# 4. Apply updated Flex recipes
composer symfony:sync-recipes --force

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

# 6. 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. The ecosystem has largely caught up as of early 2026, but niche bundles sometimes lag.

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 deprecation warnings appear in the log under realistic traffic. Critical user journeys work in a staging environment that mirrors production configuration. Performance benchmarks are equal or better than 6.4—Symfony 7 is typically 5-10% faster due to container optimizations, so a regression indicates a misconfigured service or a bundle that did not upgrade cleanly.

A Pragmatic Path Forward

For most Berlin and EU engineering teams running Symfony, the upgrade to 7.x is worth doing deliberately rather than urgently. Symfony 6.4 LTS is supported through late 2027, which means teams have a real window to plan the migration around other priorities rather than cramming it between quarters.

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.