Skip to content
AT
All articles
· 3 min read · Legacy / Yii / Laravel / Migration

Strangler-Fig in Practice: Migrating a Yii 1 Portal Without a Freeze

Rewrites fail because they demand that the business stop moving. Incremental migration lets both applications run at once — the hard part is the session, not the routing.

Any proposal that begins "we rewrite it and switch over in Q3" is a proposal to stop shipping features for two quarters and then take on all the risk in one evening. It almost never survives contact with a business that has customers.

The alternative is to run both applications simultaneously and move traffic path by path.

Routing is the easy half

A reverse proxy in front of both applications, with an explicit list of migrated paths:

location ~ ^/(dashboard|reports|invoices) {
    proxy_pass http://laravel_app;
}

location / {
    proxy_pass http://legacy_yii_app;
}

Every migrated module is one line. Rolling one back is deleting that line — which matters, because being able to revert in seconds is what makes the team willing to migrate the risky modules at all.

Sessions are the hard half

Users must not notice which application served the page, and that means one session across both. Two options, in order of preference:

Shared session store. Point both applications at the same Redis or database session backend and teach the new one to read the old one's serialisation format. Yii 1 uses PHP's native session serialisation; Laravel does not.

// Reading a legacy Yii 1 session payload from the shared store.
public function fromLegacy(string $payload): array
{
    $original = ini_set('session.serialize_handler', 'php');
    session_decode($payload);
    ini_set('session.serialize_handler', $original);

    return $_SESSION;
}

Shared cookie with a signed token. If the session formats are too far apart, issue a short-lived signed token on a cookie scoped to the parent domain and let each application resolve its own session from it. More moving parts, but it survives a session driver change on either side.

Whichever you pick, write the integration test for "log in on the old app, land on a new-app page, stay logged in" before migrating a single module. It is the failure users report loudest.

Migrate by traffic, not by tidiness

The instinct is to start with the ugliest module. Start with the highest-traffic one instead. It gets you the most production feedback per unit of risk, and it is the module where a performance win is visible enough to buy patience for the rest of the migration.

For each slice: extract the business logic out of the legacy controller into a tested service class, build the new screen against that service, ship behind a feature flag with the legacy path still reachable, then delete the legacy code once traffic has fully moved. Delete it in the same sprint. A migration that leaves both implementations in place has not reduced anything — it has doubled the surface area, and next quarter nobody will remember which one is authoritative.

AT

Anton Tofanidze

Full-Stack Engineer · B2B marketing & enterprise platforms

Get in touch

Keep reading

Read Idempotent CRM Sync: Why Your Integration Keeps Creating Duplicates
· 3 min read

Idempotent CRM Sync: Why Your Integration Keeps Creating Duplicates

Every retry in a CRM integration is a chance to create a second copy of the same lead. Here is the key design that makes retries safe — and the three places teams us...

Integrations Salesforce RabbitMQ