Upgrade to Symfony 8
In Symfony 8, $application->add() became addCommand(), a constraint no longer takes
an array of options, and #[AutowireIterator] replaced #[TaggedIterator]. Symfony 8.0 removed what 7.x
had deprecated, and it requires PHP 8.4. DressCode with the package dresscode/upgrade-symfony prepares your code for
the upgrade from Symfony 7.4 to 8 while you are still on 7.4: it rewrites the code to the new API, and after the move it
continues with what Symfony 8.1 deprecated.
The data of the package hold 217 changes that Symfony deprecated in versions 7.1 to 7.4 and removed in 8.0: DressCode rewrites 152 of them by itself and reports the other 65. Symfony 7.4, released together with 8.0 at the end of November 2025, has the same features according to UPGRADE-8.0.md, so every replacement already exists in 7.4.
What It Looks Like
A command and two functions of an application on Symfony 7.4, with the Symfony packages at 7.4.* in
composer.json:
class ExportCommand extends Command
{
public function __construct(
#[TaggedIterator('app.exporter')]
private iterable $exporters,
) {
parent::__construct();
}
public static function getDefaultName(): ?string
{
return 'app:export';
}
}
function createApplication(ExportCommand $export): Application
{
$application = new Application('shop');
$application->add($export);
return $application;
}
function nameConstraints(): array
{
return [
new NotBlank(['message' => 'Fill in the name.']),
new Length(['min' => 3, 'max' => 50]),
];
}
After dresscode upgrade today, still on Symfony 7.4:
#[AsCommand(name: 'app:export')]
class ExportCommand extends Command
{
public function __construct(
#[AutowireIterator('app.exporter')]
private iterable $exporters,
) {
parent::__construct();
}
}
function createApplication(ExportCommand $export): Application
{
$application = new Application('shop');
$application->addCommand($export);
return $application;
}
function nameConstraints(): array
{
return [
new NotBlank(message: 'Fill in the name.'),
new Length(min: 3, max: 50),
];
}
Four changes from three versions of Symfony 7: #[TaggedIterator] became #[AutowireIterator] (7.1);
the method getDefaultName() moved into the attribute #[AsCommand] and the options of the constraints
became named arguments (7.3); add() became addCommand() (7.4). DressCode rewrote the imports of both
attributes as well, and the code still runs on 7.4.
What Gets Rewritten
The tables show examples from the data of the package, each verified by a run of DressCode. The complete list is in the data, one file per component, for example validator.neon.
Deprecated in Symfony 7.1 to 7.4
| old API | new API | since |
|---|---|---|
#[TaggedIterator('app.exporter')] |
#[AutowireIterator('app.exporter')] |
symfony/dependency-injection 7.1 |
new UriSigner($secret, parameter: '_sig') |
new UriSigner($secret, hashParameter: '_sig') |
symfony/http-foundation 7.1 |
$type->isA(TypeIdentifier::STRING) |
$type->isIdentifiedBy(TypeIdentifier::STRING) |
symfony/type-info 7.2 |
getDefaultName() of a command |
#[AsCommand(name: 'app:export')] on the class |
symfony/console 7.3 |
new Length(['min' => 3, 'max' => 50]) |
new Length(min: 3, max: 50) |
symfony/validator 7.3 |
Type::iterable(Type::string(), asList: true) |
Type::list(Type::string()) |
symfony/type-info 7.3 |
$application->add($command) |
$application->addCommand($command) |
symfony/console 7.4 |
Symfony\Component\Serializer\Annotation\Groups |
Symfony\Component\Serializer\Attribute\Groups |
symfony/serializer 7.4 |
new Crawler($html, $uri, null, true) |
new Crawler($html, $uri, null) |
symfony/dom-crawler 7.4 |
new MastermindsParser() |
new NativeParser() |
symfony/html-sanitizer 7.4 |
The constraints of the validator account for 47 of these rewrites. For 46 constraint classes, DressCode turns an array of
options into named arguments, and new Choice(['a', 'b']) becomes new Choice(choices: ['a', 'b']).
Deprecated in Symfony 8.1 and 8.2
Symfony 8.1 deprecated 37 more things, and DressCode rewrites 22 of them. It applies them once composer.json
requires 8.1; with 8.0.*, it leaves them alone:
| old API | new API | since |
|---|---|---|
extends Symfony\Component\HttpKernel\DependencyInjection\Extension |
extends Symfony\Component\DependencyInjection\Extension\Extension |
symfony/http-kernel 8.1 |
new ValidatorExtension($validator, false) |
new ValidatorExtension($validator) |
symfony/form 8.1 |
mirror($source, $target, options: ['copy_on_windows' => $follow]) |
mirror($source, $target, options: ['follow_symlinks' => $follow]) |
symfony/filesystem 8.1 |
Symfony 8.2 is planned for November 2026 (Symfony 8.2). The data already hold six changes from its UPGRADE-8.2.md, and DressCode applies them once a project requires 8.2.
What Gets Reported
Where the replacement is used differently, or there is none, DressCode reports the place, leaves the code alone and says what to write instead:
Method `Symfony\Component\HttpFoundation\Request::get()` is deprecated: read the bag the value comes from, the `attributes`, `query` or `request` property, such as `query->get()`, since symfony/http-foundation 7.4.
Method `Symfony\Component\Security\Core\User\UserInterface::eraseCredentials()` is deprecated: there is no replacement; nothing calls it any more; leave the plain password out of `__serialize()` where the method erased it, since symfony/security-core 7.3.
| old API | what to write instead | since |
|---|---|---|
Symfony\Component\PropertyInfo\Type |
Symfony\Component\TypeInfo\Type, built by factories such as Type::int()
or Type::list() |
symfony/property-info 7.3 |
LazyGhostTrait |
ReflectionClass::newLazyGhost(), a native lazy ghost of PHP 8.4 |
symfony/var-exporter 7.3 |
ConfigBuilderGenerator and the fluent config builders |
a PHP array returned from the config file | symfony/config 7.4 |
XmlFileLoader of the container |
PhpFileLoader or YamlFileLoader, with the configuration rewritten in PHP or YAML |
symfony/dependency-injection 7.4 |
$request->query = new InputBag($query) |
the query parameters passed to the constructor or to initialize(),
or $request->query->set() |
symfony/http-foundation 8.1 |
Bundle::registerCommands() |
each command as a service with #[AsCommand] or the tag console.command |
symfony/http-kernel 8.1 |
ConstraintValidatorInterface::validate() |
validateInContext(), which takes the execution context too |
symfony/validator 8.1 |
Confirm such a place with the comment // dresscode:ignore <decision> -- <reason>, or rewrite it; Manual Tasks Along the Way has the details. The data do
not capture changes in YAML configuration, in Twig templates or in behavior; UPGRADE-8.0.md lists them.
Symfony 7.4 LTS or 8.x
Symfony 7.4 is a long-term support (LTS) release. It gets bug fixes until November 2028 and security fixes until November 2029, and it runs on PHP 8.2 (Symfony 7.4). The 8.x versions need PHP 8.4 and are maintained for a shorter time: support for 8.0 ended in July 2026, and 8.1 is supported until January 2027 (Symfony 8.1).
Either way, the first step is the same. On 7.4, dresscode upgrade today rewrites everything 7.1 to
7.4 deprecated, so an application that stays on the LTS has code written for the API of Symfony 8, as far as the data go, and
moving to 8.x later is a smaller step.
Step by Step
DressCode needs PHP 8.4. If your application on Symfony 7.4 runs on an older PHP, install DressCode globally, with PHPStan and the data for Symfony:
composer global require dresscode/dresscode dresscode/upgrade-symfony phpstan/phpstan
On PHP 8.4 or newer, it can be a development dependency of the project, as Installation shows.
- Start from Symfony 7.4 with a clean working tree. Run
dresscode upgrade today, and once more if it advises you to. Go through what it reports, run your tests and PHPStan, and commit. - Raise
require.phpto at least 8.4. The next run ofupgrade todaythen also fixes or reports what PHP deprecated up to 8.4, see Upgrading to a newer PHP. - Raise the Symfony packages in
composer.jsonto8.0.*; with Symfony Flex, raiseextra.symfony.requiretoo. Runcomposer update "symfony/*" -W --prefer-lowestanddresscode upgrade todayagain, then run the tests and commit. - Move on to
8.1.*the same way. Only now does DressCode rewrite what 8.1 deprecated, such as the base classExtensionof a bundle extension.
DressCode reads the version of each package from its constraint in composer.json. If a Symfony
package has only * there and leaves the version to extra.symfony.require, DressCode does not see the
lower bound and only reports the changes of that package. Write the version into the constraint, such as 7.4.*.
Upgrade Your Code Step by Step explains
why --prefer-lowest and why one version at a time.
Where to Go Next
- Symfony, all components the package covers, and the older versions.
- Upgrade Your Code Step by Step, the whole procedure with tests and commits.
- Upgrading to a newer PHP, because Symfony 8 needs PHP 8.4.