dresscode / upgrade-symfony
👔 Upgrades code written for Symfony 5.4 to 6, 7 and 8: renamed classes and methods, SensioFrameworkExtraBundle annotations to attributes, the rest reported with what to write instead.
Package info
github.com/dg/dresscode-upgrade-symfony
Type:dresscode-plugin
pkg:composer/dresscode/upgrade-symfony
Requires
- php: 8.4 - 8.6
- dresscode/dresscode: dev-master
Requires (Dev)
- nette/phpstan-rules: ^1.0
- nette/tester: ^2.6
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.2
- symfony/amqp-messenger: ^8.1
- symfony/asset: ^8.1
- symfony/asset-mapper: ^8.1
- symfony/brevo-mailer: ^8.1
- symfony/brevo-notifier: ^8.1
- symfony/browser-kit: ^8.1
- symfony/cache: ^8.1
- symfony/config: ^8.1
- symfony/console: ^8.1
- symfony/dependency-injection: ^8.1
- symfony/doctrine-bridge: ^8.1
- symfony/doctrine-messenger: ^8.1
- symfony/dom-crawler: ^8.1
- symfony/error-handler: ^8.1
- symfony/event-dispatcher: ^8.1
- symfony/expression-language: ^8.1
- symfony/filesystem: ^8.1
- symfony/finder: ^8.1
- symfony/form: ^8.1
- symfony/framework-bundle: ^8.1
- symfony/google-chat-notifier: ^8.1
- symfony/html-sanitizer: ^8.1
- symfony/http-client: ^8.1
- symfony/http-foundation: ^8.1
- symfony/http-kernel: ^8.1
- symfony/intl: ^8.1
- symfony/json-streamer: ^8.1
- symfony/lock: ^8.1
- symfony/mail-pace-mailer: ^8.1
- symfony/mailer: ^8.1
- symfony/messenger: ^8.1
- symfony/mime: ^8.1
- symfony/monolog-bridge: ^8.1
- symfony/notifier: ^8.1
- symfony/options-resolver: ^8.1
- symfony/password-hasher: ^8.1
- symfony/property-access: ^8.1
- symfony/property-info: ^8.1
- symfony/redis-messenger: ^8.1
- symfony/routing: ^8.1
- symfony/security-bundle: ^8.1
- symfony/security-core: ^8.1
- symfony/security-csrf: ^8.1
- symfony/security-http: ^8.1
- symfony/serializer: ^8.1
- symfony/sevenio-notifier: ^8.1
- symfony/slack-notifier: ^8.1
- symfony/string: ^8.1
- symfony/translation: ^8.1
- symfony/twig-bridge: ^8.1
- symfony/type-info: ^8.1
- symfony/validator: ^8.1
- symfony/var-dumper: ^8.1
- symfony/var-exporter: ^8.1
- symfony/workflow: ^8.1
- twig/twig: ^3.21
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-20 18:56:05 UTC
README
✅ Upgrades code written for Symfony 5.4 to 6, 7 and 8
✅ Turns SensioFrameworkExtraBundle annotations into attributes
✅ Reports what needs a human, with what to write instead
✅ Checked against the tests of Symfony itself
Upgrade a Symfony application without hunting for renames. Install this package, run dresscode fix, and code
written for older versions of Symfony is rewritten to the API of the versions your project stands on:
- protected static $defaultName = 'app:report'; - protected static $defaultDescription = 'Sends the daily report'; + #[AsCommand(name: 'app:report', description: 'Sends the daily report')] - $request = $this->requestStack->getMasterRequest(); + $request = $this->requestStack->getMainRequest(); - /** @Security("is_granted('ROLE_ADMIN')") */ + #[IsGranted('ROLE_ADMIN')] - return JsonResponse::create(['name' => $user->getUsername()]); + return new JsonResponse(['name' => $user->getUserIdentifier()]); - class OrderPlacedHandler implements MessageHandlerInterface + #[AsMessageHandler] + class OrderPlacedHandler
Two static properties became one attribute of the class, the expression of @Security became the role of
#[IsGranted], the imports were rewritten along with them, and not a byte more of the file touched. The package
holds the data, what each version of Symfony renamed, moved or retired; the rewriting is done by
DressCode, the PHP coding standard and upgrade tool, which comes with it.
Installation and first run
1️⃣ Install it: composer require --dev dresscode/upgrade-symfony
2️⃣ Turn the upgrade on in dresscode.neon
3️⃣ Look at the changes and make them: vendor/bin/dresscode check --diff, vendor/bin/dresscode fix
The package brings DressCode along, and DressCode finds its data by itself, but the rules the package ships are
loaded only where use names it. The upgrade is the set deprecations, and most of its rewrites need to know what
a variable is, which DressCode asks your PHPStan:
typeAnalysis: phpstan use: - dresscode/upgrade-symfony - deprecations
If your project already has a dresscode.neon, add the two keys to it. If it does not, these lines are the whole
file: with no coding standard named, DressCode upgrades the code and leaves its style alone. Then run your tests and
PHPStan, read the diff, and commit.
DressCode can keep the style of the code as well: its standard symfony writes the Symfony Coding Standards as the
@Symfony set of PHP CS Fixer has them, so use: [dresscode/upgrade-symfony, symfony, deprecations] gives one run that
both formats and upgrades.
What gets upgraded
None of it is search and replace. DressCode asks PHPStan what each variable is, so getUsername() is rewritten where
it is called on a user and nowhere else. PHPStan answers with your phpstan.neon and its extensions, the one for
Symfony included. What the package rewrites:
- renamed classes and interfaces:
Symfony\Component\Security\Core\SecuritytoSymfony\Bundle\SecurityBundle\Security,UsernameNotFoundExceptiontoUserNotFoundException, the encoders ofsecurity-coreto the password hashers, the bridge of Sendinblue to Brevo, with the import, the type,new,instanceofand attributes, - renamed members:
getMasterRequest()togetMainRequest(),HttpKernelInterface::MASTER_REQUESTtoMAIN_REQUEST,getUsername()togetUserIdentifier(),Application::add()toaddCommand(), - calls of another shape: the static
create()of responses tonew, a renamed parameter (new UriSigner($secret, parameter: ...)tohashParameter:), a renamed key of an array of options,ref()of the container configuration toservice(), - annotations of SensioFrameworkExtraBundle to the attributes of the framework:
@Route,@Cache,@IsGranted,@Template, and@Securitywhose expression only callsis_granted(), - other annotations to attributes: those of routing and the serializer, the constraints of the validator and
@UniqueEntity, and@requiredto#[Required], - a member of a class to an attribute:
$defaultNameand$defaultDescriptionof a command, and an overriddengetDefaultName(), to#[AsCommand], the interfaceMessageHandlerInterfaceto#[AsMessageHandler], - resolvers of controller arguments written against
ArgumentValueResolverInterfacetoValueResolverInterface,supports()becoming the guard at the top ofresolve(), - signatures of children of framework classes, the return types and parameters a newer version declares
(
execute(): intof a command), written from the installed code with no data needed.
The rewritten code takes the shape of the file around it, or of your coding standard, if the configuration names one.
What is left to you
Where there is no replacement, or one that needs a decision, DressCode does not guess. It reports the place and says what to write instead:
Method `Symfony\Bundle\FrameworkBundle\Controller\AbstractController::getDoctrine()` is forbidden: use a constructor or action argument typed with `Doctrine\Persistence\ManagerRegistry`
Method `Symfony\Component\HttpFoundation\Request::get()` is forbidden: read the bag the value comes from, the `attributes`, `query` or `request` property, such as `query->get()`
An upgrade is a rewrite plus a list of what remains, and the list is half of its value. Every such sentence is written as an instruction, so the list can be worked through by you or handed to an AI coding agent as it is.
A few rewrites could change what the code does, such as a return type added to a method whose body may return
something else. Those wait for your consent: dresscode fix --ask-risky asks about each one with its diff,
--fix-risky makes them all.
One major version at a time
The data of each component apply only when your project has it, and only the sections of the versions it stands
on: the lowest version the constraint in composer.json allows. A project requiring symfony/symfony has every
component it replaces. So an upgrade goes the way you would do it by hand: raise the constraint to the next major
version, composer update, dresscode fix, tests, commit, and on to the next one. In each step the data meet the
installed components, whose @deprecated annotations tell DressCode what the next version will remove.
Code can also be upgraded before the components are, with the key targets, which names the version to write for:
targets: symfony/http-kernel: '7.4' symfony/security-core: '7.4'
dresscode config shows for every component the version the data start from and the versions a further update
would lead to.
Where the data come from
The data come from the UPGRADE-*.md guides of Symfony, each entry checked against the code of the component at
its tags: where the guide and the code differ, the code decides. They are checked against the installed components,
so a replacement that does not exist cannot get in, and each component has a sample of old code that the data must
turn into the expected new code.
Then the data run over the tests of the components themselves, code written for the current API, and must leave them exactly as they are. An entry that would touch correct code does not get in.
Compared to Rector
Rector is the usual answer to an upgrade, and its sets for Symfony reach further back than this package, which starts at 6.0. The difference is in the shape of the work. Where Rector changes the structure of a statement, it prints the statement anew, so it needs a coding standard tool after it, and a rule of Rector either rewrites the code or says nothing. DressCode is one tool, with one configuration and one diff: the rewrite keeps the formatting of everything around it, and what cannot be rewritten is reported with what to do. It is also faster, the types from PHPStan included.
An entry is a line of text, whatever it rewrites:
since 7.4: replacedMembers: Symfony\Component\Console\Application::add: addCommand since 6.1: attributeForMember: Symfony\Component\Console\Command\Command::$defaultName: 'Symfony\Component\Console\Attribute\AsCommand(name: $value)'
Symfony components and versions covered
More than 650 entries, from Symfony 6.0 to the current version, for these components: symfony/asset,
symfony/asset-mapper, symfony/browser-kit, symfony/cache, symfony/config, symfony/console,
symfony/dependency-injection, symfony/doctrine-bridge, symfony/dom-crawler, symfony/event-dispatcher,
symfony/expression-language, symfony/filesystem, symfony/finder, symfony/form, symfony/framework-bundle,
symfony/html-sanitizer, symfony/http-client, symfony/http-foundation, symfony/http-kernel, symfony/intl,
symfony/json-streamer, symfony/lock, symfony/mailer, symfony/messenger, symfony/mime,
symfony/monolog-bridge, symfony/notifier, symfony/options-resolver, symfony/property-access,
symfony/property-info, symfony/routing, symfony/security-bundle, symfony/security-core,
symfony/security-csrf, symfony/security-http, symfony/serializer, symfony/string, symfony/translation,
symfony/twig-bridge, symfony/type-info, symfony/validator, symfony/var-dumper, symfony/var-exporter and
symfony/workflow, and the bridges symfony/brevo-mailer, symfony/brevo-notifier, symfony/google-chat-notifier,
symfony/mail-pace-mailer, symfony/sevenio-notifier and symfony/slack-notifier.
The data of a retired bridge are written under the one that replaced it, so those of symfony/sendinblue-mailer
apply to a project that has symfony/brevo-mailer, and the annotations of SensioFrameworkExtraBundle under the
components whose attributes replace them. The manual lists the versions
of each.
Limits
- Most rewrites need PHPStan. Without
typeAnalysis: phpstan, only classes, functions and annotations are rewritten. - DressCode runs on PHP 8.4 to 8.6. The code it upgrades may be written for PHP 8.0 and newer, but the package is installed into the project, so the project has to install on PHP 8.4 or newer.
- The data fix the code, not its meaning. A change of behavior, the configuration in YAML and Twig templates are
not in the data; the
UPGRADE-*.mdguides of Symfony describe them. Run your tests after a fix.
Other ecosystems
| package | upgrades |
|---|---|
dresscode/upgrade-nette |
every Nette library, most from 3.0 |
dresscode/upgrade-symfony |
the components and bridges of Symfony, from 6.0 |
dresscode/upgrade-laravel |
the Laravel framework from 6, and the attributes of Laravel 13 |
dresscode/upgrade-deegee |
Dibi and Texy |
A package like these can be written for any library; the manual says how.
Development
The components the data are about are in require-dev, so the data are checked against their installed versions:
php tests/check.php <component>lintsupgrading/<component>.neonand runs its sample,tests/samples/<component>.code, comparing the result with.expectedand.violations,php tests/check.php <component> --updatewrites those two from the run; read the diff, it is what the data do,vendor/bin/tester testsruns all of it,php tests/corpus.php <symfony>/src/Symfony <package>...runs the data over the tests of the components, which they must leave as they are.