โโWe decided to code our own PHP framework when founding AssoConnect many years ago. That was a great yet challenging way to learn how many critical parts of a framework work ๐ค
But maintaining it was also too intense so we decided to migrate to the Symfony framework backed by a strong community.
We aimed to get as many end-to-end legacy-free flows as possible to lay down strong foundations to build modern code ASAP.
This means Symfony must be exposed and visible to the world: it can't start as a small piece within our old framework.
So we first changed the routing: Symfony will be from now on the entry point

If a modern controller supports the requested route, then it serves it
If not, then the request is routed to our legacy framework which handles it
legacy_all:
path: /{path}
controller: App\Controller\LegacyController::index
Running php bin/console debug:router shows that the routing rule comes last โ
Then we established a base rule for dependency injection:
The container is passed along the Request object so old code can access modern dependencies through public aliases.
This makes it possible to have an end-to-end flow without a line of old code ๐
And old code doesn't stain the new code โ
It also designs a strategy to migrate the codebase:

Our proprietary ORM uses entities close to Doctrine 1 ones (they persist themselves) which is very different from the Doctrine 2 logic (the ORM is in charge of persisting entities).
Our entities also have built-in lifecycle events and validation.
So migrating overnight wasn't an option ๐ฅ
We set a second rule for migrating entities in line with the main plan:
โ
Old tables can sync (create, update, delete) modern tables
โ Modern tables cannot sync back to old tables
โ ๏ธ This creates a strong constraint: a modern table (and the related entity) is read-only as long as the old table exists.
It wasn't obvious at first sight but it actually makes sense when migrating data
You first expose a new interface (modern tables and entities).
Then you migrate read services.
Then you migrate write services (you should only have a few ones anyway or you have duplicate code or bad responsibilities segregation).
We are still migrating and our strategy is sometimes frustrating: rules may require us to migrate a part that is not a business priority.
But it helps to keep a clean modern codebase ๐ซ
And it has a rewarding upside: the more code you migrate, the more code turns migratable ๐คฉ