Symfony 7 was released in November 2023 and marks an important milestone in Symfony's history: PHP 8.2 is now a requirement, numerous deprecations from Symfony 5 and 6 have been permanently removed, and the framework has been cleaned up in many areas. If you are still developing on Symfony 6.4, you should plan the migration to Symfony 7 — Symfony 6.4 will receive security updates until November 2027, but new features are only available in Symfony 7.
This guide describes the migration based on a typical Symfony 6 project with Doctrine ORM, API Platform and the Symfony Security Bundle.
Prerequisites
Before getting started, some basic requirements must be met:
- PHP 8.2 or higher is mandatory. Symfony 7 actively uses PHP 8.2 features such as
readonlyproperties. - The project should be running on Symfony 6.4 (the last 6.x version), because all Symfony 7 deprecation warnings are already visible there.
- PHPStan or Psalm help to find hidden issues.
First, check whether all deprecation warnings from Symfony 6 have been resolved:
ddev exec bin/console debug:container --deprecations
Ideally, this list should be empty before attempting the version jump.
Step 1: Adjust composer.json
The actual upgrade begins in the composer.json. Raise the Symfony version constraints to ^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"
}
}
Then run the update:
ddev exec composer update "symfony/*" --with-all-dependencies
Composer will report conflicts if other packages still have incompatible Symfony version requirements. Common candidates are older bundles or API Platform in an older major version.
Step 2: Removed Features and Breaking Changes
Symfony 7 has removed all classes, methods and parameters that were marked as @deprecated in Symfony 6. Here are the most common breaking changes:
AbstractController: getDoctrine() Removed
In Symfony 6 you could still use $this->getDoctrine() in the controller. This is gone in Symfony 7. The solution: inject EntityManagerInterface or ManagerRegistry via dependency injection.
<?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 Annotations: @Route Replaced by #[Route]
The symfony/routing bundle has completely removed the old Doctrine annotations. Anyone still using @Route("/path") must switch to PHP attributes:
<?php
declare(strict_types=1);
namespace App\Controller;
// Before (Symfony 6, deprecated):
// use Symfony\Component\Routing\Annotation\Route;
// After (Symfony 7):
use Symfony\Component\Routing\Attribute\Route;
class MyController
{
#[Route('/my-path', name: 'my_route', methods: ['GET'])]
public function index(): Response
{
// ...
}
}
Security: AuthenticationEntryPointInterface Changed
The security subsystem has received some interface changes. Anyone who has implemented custom authenticators must check whether the method signatures still match.
Serializer: ContextAwareNormalizerInterface Removed
Custom normalizers that implemented this interface must be updated to the new approach, where the $context parameter is passed directly to normalize() and supportsNormalization().
Step 3: Leverage PHP 8.2 Features
Symfony 7 requires PHP 8.2 — this is a good opportunity to modernize your own code.
Readonly Properties and Readonly Classes
Readonly properties were introduced in PHP 8.1. PHP 8.2 extends this concept with readonly classes — making all properties of a class automatically readonly:
<?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 for Asynchronous Processing
PHP 8.1 introduced Fibers — a complete implementation for cooperative multitasking at a low level. Fibers allow functions to be suspended at arbitrary points and resumed later, which is especially useful for asynchronous I/O operations. Frameworks like ReactPHP and AMPHP use Fibers as the foundation for their event loops.
Step 4: Update Symfony Flex Recipes
Symfony Flex manages configuration files. After the version upgrade, check whether new recipes are available:
ddev exec composer recipes:update
This shows which bundles provide new configuration templates. Not every change needs to be adopted, but it is worth reviewing the diffs.
Step 5: Fix Deprecation Warnings in Symfony 7
After upgrading to Symfony 7, new deprecations will appear — these are hints about features that will be removed in Symfony 8. You can keep track using the Profiler in dev mode or with PHPStan and the Symfony PHPStan plugin:
ddev exec composer require --dev phpstan/phpstan phpstan/phpstan-symfony
ddev exec vendor/bin/phpstan analyse src
Rector: Automate the Upgrade
Many of the changes described above can be automated with Rector. Rector is a PHP refactoring tool that analyzes the Abstract Syntax Tree (AST) and performs rule-based code transformations.
Installation
ddev exec composer require rector/rector --dev
The rector/rector-symfony package is already included and does not need to be installed separately.
Configuration
Rector automatically detects the installed Symfony version from vendor/composer/installed.json and applies the appropriate rules. A rector.php in the project root:
<?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,
);
What Rector Handles Automatically
Rector knows the specific Symfony deprecations and can automatically rewrite many of them:
- Remove
getDoctrine()and replace with constructor injection - Convert
@Routeannotations to#[Route]attributes - Replace
$defaultName/$defaultDescriptionin console commands with#[AsCommand] - Simplify
$form->createView()in render calls - Add return type declarations that Symfony 7 requires
- Update renamed classes automatically (e.g.
RequestMatchertoChainRequestMatcher)
Workflow
# 1. Dry run: Shows planned changes without executing them
ddev exec vendor/bin/rector process --dry-run
# 2. Apply changes
ddev exec vendor/bin/rector process
# 3. Fix code style (Rector works at AST level, formatting may suffer)
ddev exec vendor/bin/php-cs-fixer fix
# 4. Run tests
ddev exec vendor/bin/phpunit
What Rector Cannot Do
Rector only works with PHP code. The following must be done manually:
- YAML/XML configuration: Changes to
services.yaml,routes.yaml,security.yaml - Twig templates: Renamed filters, changed functions
- Third-party bundles: Incompatibilities with less common bundles
- Behavioral changes: When the semantics of a method have changed (e.g. exceptions instead of
false) - Complex interface changes: When custom implementations require new signatures
In practice, Rector covers 60-80% of the mechanical upgrade work. The remaining adjustments are those that actually require human judgment.
Common Pitfalls
Third-party bundles: Not all bundles are immediately compatible with Symfony 7. Especially older bundles without active maintenance can cause problems. composer outdated and a look at the GitHub issues of the respective bundle help here.
API Platform 3.x: API Platform 2.x is not compatible with Symfony 7. Anyone using API Platform must first migrate to version 3.x.
Doctrine bundles: doctrine/doctrine-bundle 2.x is compatible with Symfony 7, but you should check the release notes.
Conclusion
Migrating from Symfony 6 to 7 is manageable if you have already kept the project up to date with Symfony 6.4 and resolved all deprecation warnings. The biggest efforts come from removed legacy APIs like getDoctrine() and old annotation routes. If you approach the migration in a structured way — PHP 8.2, clean up deprecations, check third-party dependencies — a smaller project is typically migrated within a day.
For larger projects, a feature branch with automated tests as a safety net is recommended: ddev exec vendor/bin/phpunit should stay green after the upgrade.
Kommentare
Kommentare werden von Remark42 bereitgestellt. Beim Laden werden Daten an unseren Kommentar-Server übertragen.