When an upgrade stops being routine
An upgrade is routine when you are one minor version behind. It stops being routine when the product is several majors behind, the PHP version is out of support, a few bundles were abandoned by their authors, the deprecation log runs to thousands of lines and nobody wants to be the one who deploys the upgrade. That is the situation we are usually asked into.
How we do it
- Measure the gap first: Symfony and PHP versions, deprecations, dependencies, and bundles with no maintained version. You get the number of steps and what blocks each one before any code changes.
- Put tests around current behavior, on the modules that change first. Generated with AI help, verified by a person, kept in CI.
- Clear deprecations while still on the current version, module by module, and release each piece. Most of the work of an upgrade happens here, in small changes your team can review.
- Move one major version at a time, in production, not five at once. Each step is a normal release, not an event.
- Replace abandoned bundles one at a time, behind the same interface, so the rest of the code does not notice.
- Keep static analysis and coverage gates in CI afterwards, so the codebase stays current instead of drifting back.
One honest detail: the framework version is shared by the whole application, so the final switch happens for everything at once. What goes module by module is the preparation, and the preparation is where the risk is. Done this way, the switch itself is a small release.
Cutover and rollback
The final switch ships like any other release, with the way back agreed before it happens, not decided during. Database changes stay compatible with the previous version until the old code is gone, so the previous build can be redeployed on the same data. What triggers a rollback, who decides it and how long the old version stays deployable are written down in the plan. For the Zend Framework migration, the way back was the shadow system itself: the old code kept running until the new one produced identical output, and customers moved in groups with the old system still running behind them.
Moving off another framework
The same approach works for leaving an older framework entirely. We moved an enterprise SaaS platform from Zend Framework and an old Node.js version to Symfony 7 in 3 months: the new code ran as a shadow system next to the old one on the same input, outputs were compared until identical, then customers moved over gradually. Zero production incidents were caused by a cutover, and fewer than 15 bugs, all minor, came back afterwards.
How it is scoped
A two-week technical audit produces the upgrade plan: steps, blockers, order and effort. An AI-assisted delivery pilot of 4-6 weeks takes the first module through to production, to prove the plan. A monthly retainer carries the rest, module by module. Scope and price come in writing after a 20-minute conversation, and you can also take the audit plan and execute it with your own team.