The deprecation counter in the profiler, three digits. That is where one project stands this week, and that number is the whole Symfony 6 migration.

Symfony shipped 5.4 and 6.0 together at the end of November, same code, same day. 4.4 and 5.0 worked exactly like this two years ago, so the story is rehearsed. 6.0 is 5.4 with the deprecated code deleted. You never migrate to a major. You migrate to the last minor, and the major is a formality.

The plan fits in three sentences. Move to 5.4 and get the deprecation count to zero, the PHPUnit bridge and the profiler show every hit. Raise the baseline, 6.0 wants PHP 8, which for us was done back in spring. Then flip the constraints to 6.0 and expect an anticlimax.

The one place 6.0 will really poke you is native types. Symfony added parameter and return types across its interfaces, so every place where you extend or implement a framework class must now declare matching types. Mechanical work, the deprecation layer in 5.4 warns about most of it, but on an old codebase there are more such places than you remember.

The discipline I insist on: the upgrade branch contains compatibility changes only. Nothing else. Mid-upgrade you will see old sins everywhere, the service that should be rewritten, the bundle we stopped needing in 2019. Write them down, walk past. A pull request that says “Symfony 6” and touches four hundred files for six reasons cannot be reviewed and cannot be reverted. Boring diff, quick review, calm deploy.

Improvements get their own branches next week, on a fresh major, with deprecations at zero. Best ground for refactoring you will ever get.

I have already rewritten one service inside the upgrade branch. I am moving it out tonight, before anyone sees the diff.