Muhammad Haris

Guitar Tricks

Legacy modernisation on a platform serving six million users

State
Status: Live
Role
Contributing engineer
Year
2026
Stack
  • PHP

Contributed to migrating a long-lived PHP codebase onto a current version, writing the unit tests that proved the migration preserved behaviour, and improving the payment gateway. The platform has six million active users, so none of it could happen behind a maintenance window.

The problem

A platform with six million active users was running on a PHP codebase old enough that the language underneath it had moved on. Staying put means an ageing runtime, security patches that stop arriving and a shrinking pool of people willing to work on it. Moving means changing code whose only specification is itself, on a system nobody can take offline, where the paying path runs straight through the middle of the change.

Constraints

  • Six million active users. There is no maintenance window, and no version of this that ships as a big-bang cutover.
  • A long-lived codebase with thin test coverage, so there was no existing safety net to lean on while changing it.
  • Payments sit inside the blast radius. A migration bug there is not a defect report, it is money.

Architecture

The work was a version migration of an existing PHP application rather than a rewrite, carried out alongside a test suite written specifically to hold the old behaviour still while the code underneath it moved. Payment gateway handling was improved as part of the same pass, since it was both the highest-risk path in the migration and the one with the least tolerance for a silent regression.

Decisions and what they cost

Write unit tests against the existing behaviour as part of the migration, not after it.

Why
On a codebase this old the specification is the code. There is no document that says what correct looks like, so the only way to know a migration preserved behaviour is to pin that behaviour down first and then let the tests fail when it changes. Migrating first and testing afterwards only tells you what the new code does, which is the question you already knew the answer to.
What it cost
It slows the migration down, and some of that effort is spent writing tests for code that is about to be replaced, so a portion of the suite is disposable by design. You are also testing behaviour rather than intent, which means any bug faithfully preserved by the migration gets preserved by the tests too.

What it does

Version migration, not a rewrite

The codebase moved onto a current PHP version rather than being replaced. At six million users a rewrite is a multi-year bet against a system that already works; a migration is a series of changes each small enough to verify.

Tests as the safety net

Unit tests were written to cover the functionality being moved and to verify the migration itself, so a regression surfaced in a test run rather than in production traffic.

Payment gateway improvements

Gateway handling was improved during the same pass. It is the path where a silent regression costs the most, so it got attention proportional to the risk rather than proportional to its size.

What I would change

The honest limit of behaviour-pinning tests is that they cannot tell a feature from a bug. Anything the old code got quietly wrong, the suite locks in and the migration carries forward intact. Given the same job again I would want a short pass to separate the behaviour worth preserving from the behaviour that was only ever an accident, before the tests make both of them permanent.