and what's removed?
Symfony 7 is the first major version since Symfony 5 to set PHP 8.2 as the minimum requirement, and it finally removes a range of long-deprecated APIs. At the same time, Symfony 7 brings genuine innovations: the ClockInterface standard, the AssetMapper as a webpack-free alternative, reworked DI attributes and a noticeably leaner core. This article shows what is actually new, what has been removed, and what the migration from 6.4 to 7 looks like.
Table of Contents
- 1. Symfony 7 at a glance: what has fundamentally changed?
- 2. PHP 8.2 as the minimum requirement: concrete impacts
- 3. The Clock component: testable handling of time
- 4. AssetMapper: JavaScript without Node.js and webpack
- 5. New DI attributes: Autowire, AutowireIterator and more
- 6. What's removed: deleted components and classes
- 7. HTTP kernel and request mapping: new possibilities
- 8. Security improvements in Symfony 7
- 9. Symfony 6.4 vs. Symfony 7: feature comparison
- 10. Summary: is the migration worth it?
- 11. FAQ
1. Symfony 7 at a glance: what has fundamentally changed?
Symfony 7 follows the well established release cycle: Symfony 6.4 is the LTS version with security support until 2027, and Symfony 7 is the new major release that removes all APIs marked deprecated in 6.x while introducing new capabilities at the same time. The upgrade model is the same as with every Symfony major release: first upgrade to 6.4 and fix all deprecation warnings, then the jump to Symfony 7 holds no surprises. Projects that have already cleanly migrated to 6.4 typically only need to update the composer constraints.
The overarching theme of Symfony 7 is reduction and clarity: less bridge code for old PHP versions, fewer abstraction layers over outdated patterns, more native PHP 8 features in the core. This shows up in concrete numbers: around 50 classes and interfaces that were marked deprecated in Symfony 6.x have been removed in Symfony 7. At the same time, classes optimized for PHP 8 features such as fibers, enums and readonly properties have been anchored in the core. The result is a framework that assumes modern PHP as a baseline instead of treating it as an optional extra.
2. PHP 8.2 as the minimum requirement: concrete impacts
PHP 8.2 as the minimum requirement means that Symfony 7 depends on several PHP 8.2 features: readonly class properties, disjunctive normal form types (DNF types), and stricter typing for some framework classes. The practical effect: Symfony core classes can now use readonly on property declarations without having to offer setter methods. This applies in particular to value objects in the framework core such as SplFileInfo wrappers and request attribute classes.
The PHP 8.2 requirement implicitly excludes PHP 8.0 and 8.1. Teams still on PHP 8.1 cannot use Symfony 7; they need to stay on Symfony 6.4. The upgrade path is clear: upgrade PHP to 8.2, migrate to Symfony 6.4, then upgrade to Symfony 7. In practice, most well maintained Symfony projects already run on PHP 8.2 or 8.3, because PHPStan and other analysis tools offer better support for PHP 8.2+. For these projects, the Symfony 7 upgrade is the natural next step after a successful switch to the 6.4 LTS release.
<?php
// composer.json, minimum requirements for Symfony 7
// {
// "require": {
// "php": ">=8.2",
// "symfony/framework-bundle": "^7.0"
// }
// }
// PHP 8.2 readonly classes, now used in Symfony 7 core value objects
readonly class ProductId
{
public function __construct(
public readonly int $value,
) {}
}
// PHP 8.2 DNF types, used in Symfony 7 interfaces
function process((Countable&Traversable)|null $collection): void
{
// DNF type: (Countable AND Traversable) OR null
}
// PHP 8.2 enum support, Symfony 7 uses enums for framework states
enum HttpMethod: string
{
case GET = 'GET';
case POST = 'POST';
case PUT = 'PUT';
case PATCH = 'PATCH';
case DELETE = 'DELETE';
}
// Migration check: find all deprecated APIs in your project
// composer require --dev symfony/deprecation-contracts
// Run: php bin/console debug:container --deprecated
// Then: php bin/console lint:container (Symfony 7 pre-check)
3. The Clock component: testable handling of time
The symfony/clock component is one of the most significant additions in Symfony 7. It solves a fundamental testability problem: code that directly uses new \DateTime(), time() or Carbon::now() cannot be tested without manipulating the clock. The ClockInterface from PSR-20 defines a single method, now(): DateTimeImmutable. The Symfony Clock component ships three implementations for it: NativeClock for production, MockClock for tests, and MonotonicClock for measuring time differences.
In production code, you inject ClockInterface as a dependency instead of calling new \DateTime() directly. Symfony 7 adds the #[Autowire('@clock')] alias for this, which injects the NativeClock implementation without any explicit services.yaml configuration. In tests, you replace the NativeClock with a MockClock that is set to a fixed time or that you can advance manually. This enables deterministic testing of time-dependent business logic, such as expiry checks, scheduler logic and timestamp calculations, without external libraries or global state manipulation. Symfony 7 integrates ClockInterface deeply into its own components: Validator, Messenger and Cache already use the clock abstraction internally.
4. AssetMapper: JavaScript without Node.js and webpack
The AssetMapper is one of the most discussed innovations that Symfony 7 brings as the default frontend solution. It replaces Webpack Encore as the recommended asset pipeline for new Symfony projects and works without Node.js, without npm and without a build step. The AssetMapper reads JavaScript files using native ES module imports, adds a content hash for cache busting, and serves them directly from an assets/ directory. In production, the files are wired up via importmap tags, which browsers can resolve natively as ESM imports.
The combination of AssetMapper and importmaps enables using npm packages directly in the browser, without a bundling step. php bin/console importmap:require stimulus loads Stimulus from a CDN and registers it in importmap.php. The package is available on the next page load. For projects that already use Webpack Encore, Encore remains fully supported in Symfony 7; AssetMapper is simply the new default recommendation for fresh installs via symfony new --webapp. TypeScript, CSS preprocessing and complex build pipelines remain the domain of Webpack Encore or Vite; the AssetMapper is deliberately kept lean.
<?php
// Symfony 7 Clock, injectable time abstraction for testable code
declare(strict_types=1);
namespace App\Service;
use Symfony\Component\Clock\ClockInterface;
/**
* Subscription service, uses ClockInterface for testable expiry checks.
*/
final class SubscriptionService
{
public function __construct(
// Inject ClockInterface, NativeClock in production, MockClock in tests
private readonly ClockInterface $clock,
private readonly SubscriptionRepository $repository,
) {}
/**
* Check if a subscription is still active at the current time.
*/
public function isActive(int $subscriptionId): bool
{
$subscription = $this->repository->find($subscriptionId);
if (!$subscription) {
return false;
}
// ClockInterface::now() returns DateTimeImmutable, fully testable
return $subscription->getExpiresAt() > $this->clock->now();
}
/**
* Extend subscription by 30 days from now.
*/
public function extend(int $subscriptionId): void
{
$subscription = $this->repository->find($subscriptionId);
// No direct new \DateTime(), deterministic in tests
$newExpiry = $this->clock->now()->modify('+30 days');
$subscription->setExpiresAt($newExpiry);
$this->repository->save($subscription);
}
}
// Test with MockClock, no global date manipulation needed
// $clock = new MockClock(new \DateTimeImmutable('2026-01-01 00:00:00'));
// $service = new SubscriptionService($clock, $repository);
// $clock->modify('+31 days'); // advance time to test expiry
5. New DI attributes: Autowire, AutowireIterator and more
In Symfony 7, the dependency injection attributes from Symfony 6.x have been fully stabilized and extended with new options. #[Autowire(lazy: true)] creates a lazy proxy for the injected service; the service is only instantiated on the first method call, not when the container is built. This improves boot performance for services that are rarely used or only used under certain conditions. #[AutowireCallable] injects a service as a callable, which is useful for factory patterns where the service should only be fetched from the container at runtime.
#[Target] is a new DI attribute in Symfony 7 that gives more precise control over the resolution of tagged services. When several services implement the same interface, #[Target('primary')] selects specifically the service marked as primary, without changing the interface name or writing manual services.yaml entries. This makes multi-implementation scenarios, such as A/B testing of service strategies or environment-specific implementations, noticeably cleaner. #[AsAlias] has also been strengthened: it now accepts multiple interface IDs on a single class, for cases where a service should be published under different interface names in the container.
6. What's removed: deleted components and classes
Symfony 7 removes all APIs that were marked deprecated in Symfony 5.x and 6.x. The most important removals affect: the AnnotationReader-based system from doctrine/annotations is no longer present as a fallback in the Symfony core; native PHP attributes are now the only supported way. The AbstractController::getDoctrine() shortcut has been removed; inject EntityManagerInterface or ManagerRegistry directly instead. The ContainerAwareInterface and ContainerAwareTrait have been removed from the framework bundle; the service locator pattern via ServiceSubscriberInterface is the correct replacement.
The sensio/framework-extra-bundle is finally superfluous with Symfony 7: @Template, @ParamConverter and @Cache already had native alternatives in Symfony 6.x, which are now fully stabilized. Anyone still using @ParamConverter migrates to #[MapEntity] (for Doctrine entities) or to a custom value resolver with #[ValueResolver]. The WebTestCase::createClient() behaviour was changed slightly: clients are now isolated by default, which reduces test interference caused by shared state. The Serializer component removed a few outdated normalizer interfaces and unified the normalizer hierarchy.
7. HTTP kernel and request mapping: new possibilities
The HTTP kernel in Symfony 7 brings improved support for the newer request mapping via #[MapQueryString], #[MapRequestPayload] and #[MapUploadedFile]. These attributes, introduced in Symfony 6.3, are fully stable in Symfony 7 and documented as the standard pattern for request handling. The ArgumentResolver was refactored internally to make the new value resolver mechanism more efficient: the resolver chain is now resolved when the container is compiled, not during request handling, which leads to less overhead in high-load scenarios.
The #[WithInput] attribute internally combines MapQueryString and MapRequestPayload in Symfony 7 for controller methods that process both the query string and the body. The Request object receives new helper methods in Symfony 7 for working with content negotiation and Accept headers, useful for APIs that support multiple formats. The EventDispatcher gains support for typed events that no longer need string names: the event type is the identifier, which guarantees typo safety in event subscriptions. This is one of the smaller but, in practice, important improvements in Symfony 7.
8. Security improvements in Symfony 7
The security system in Symfony 7 receives several improvements around authenticators and token handling. The AccessTokenAuthenticator has been reworked and now supports bearer token authentication for APIs out of the box, without any additional bundles. Configuration in security.yaml is directly possible via the new access_token keyword. Symfony 7 also improves the password hasher: the auto algorithm now automatically selects bcrypt, argon2i or argon2id based on the available PHP extensions.
The #[IsGranted] attribute gains the exceptionCode parameter in Symfony 7, which lets you precisely control the HTTP status code on access denied, for example 404 instead of 403 for security-through-obscurity on sensitive resources. The LoginThrottling mechanism is stabilized as a core feature: with simple configuration in security.yaml, Symfony 7 limits login attempts per IP and per user without any external rate limiting packages. These hardening measures are particularly relevant for public-facing applications and save you configuring external middleware.
9. Symfony 6.4 vs. Symfony 7: feature comparison
The decision between Symfony 6.4 LTS and Symfony 7 depends on project lifetime, team size and PHP version. Here is a direct comparison of the most important points.
| Aspect | Symfony 6.4 LTS | Symfony 7 | Recommendation |
|---|---|---|---|
| Minimum PHP version | PHP 8.1+ | PHP 8.2+ | Symfony 7 if you're already on PHP 8.2+ |
| Security support until | 2027 (LTS) | 2026 (standard) | Long-term projects: 6.4 or 7.4 LTS |
| AssetMapper | Backport from 6.3 | Stable, default | New projects: Symfony 7 + AssetMapper |
| Clock component | Backport available | Core integration | Symfony 7 for deeper clock integration |
| doctrine/annotations | Fallback available | Removed, PHP attributes | Migrate to PHP attributes before upgrading |
For existing projects on Symfony 5.x or 6.x, the recommended path is: first upgrade to 6.4, eliminate all deprecation warnings (in particular removing doctrine/annotations, replacing getDoctrine(), uninstalling sensio/framework-extra-bundle), then upgrade to Symfony 7. This two-stage approach avoids having to debug breaking changes and feature changes at the same time. Anyone on a clean 6.4 baseline will not encounter any nasty surprises during the Symfony 7 upgrade.
Mironsoft
Symfony migration, upgrade consulting and PHP 8.2+ modernization
Planning and running a migration to Symfony 7?
We analyze your existing Symfony project for deprecations, build a structured migration plan, and carry out the upgrade to Symfony 7 with PHP 8.2+, with no unplanned downtime and a complete test coverage report.
Deprecation audit
Complete analysis of all Symfony deprecations and identification of breaking-change risks
Migration roadmap
Prioritized staged plan: stabilize 6.4 LTS first, then upgrade to Symfony 7 without surprises
Upgrade execution
Hands-on migration including PHP 8.2+, PHPStan integration and full test coverage
10. Summary: is the migration worth it?
Symfony 7 is the clear recommendation for new projects with PHP 8.2+. The Clock component, the stabilized AssetMapper, the fully native PHP attributes and the leaner core are genuine improvements, not marketing features. For existing projects on Symfony 6.4 LTS, the upgrade is optional until 2027; anyone with well maintained code on 6.4 can postpone the switch to the next LTS cycle. However, anyone coming from Symfony 5.x or older 6.x versions should plan the path directly via 6.4 to Symfony 7.
The most important lesson from past Symfony major releases applies here too: the upgrade is easier the more consistently deprecation warnings were fixed during ongoing operation. A project that has relied on doctrine/annotations, sensio/framework-extra-bundle and getDoctrine() calls for years needs an orderly migration strategy. A project that has consistently fixed deprecations since Symfony 6.2 is on Symfony 7 within half a working day.
Symfony 7: What's New and What's Removed, the Key Points at a Glance
New in Symfony 7
PHP 8.2+ required, Clock component (PSR-20), AssetMapper as default, stabilized DI attributes (#[Autowire(lazy:true)], #[Target]), AccessTokenAuthenticator.
What's removed
doctrine/annotations fallback removed, getDoctrine() shortcut gone, sensio/framework-extra-bundle obsolete, ContainerAwareTrait removed, AbstractController shortcuts reduced.
Migration strategy
Migrate to Symfony 6.4 LTS first, fix all deprecation warnings, then upgrade to Symfony 7. PHP 8.2+ and deprecation-free code are prerequisites.
Decision guide
New projects: Symfony 7. Long-term maintenance: 6.4 LTS until 2027. Migration: two-stage via 6.4, upgrade PHP first, then Symfony.