Skip to content
Beacon CodersBeacon Coders

Migrating a Legacy PHP Application Without a Rewrite

Placeholder image (portrait-1x1) — replace before launch — Vishal Kumar
Vishal Kumar
5 March 20264 min read
Placeholder image (hero-16x9) — replace before launch — migrating-legacy-php-without-rewrite

Key takeaways

  • A full rewrite requires the old and new systems to reach feature parity before cutover, which is a much larger and riskier undertaking than most teams initially estimate.
  • The strangler fig pattern — incrementally replacing pieces of the old system behind a shared interface — lets you ship real improvements continuously rather than betting everything on a single cutover date.
  • Not every part of a legacy application needs modernising; some genuinely working, low-risk code is better left alone than rewritten for its own sake.
  • A technical audit before any migration work starts is what determines whether incremental migration or a full rewrite is actually the right call for your specific codebase.
  • The biggest hidden risk in legacy PHP migration is undocumented business logic buried in old code that nobody currently understands.

Full rewrites of legacy applications have a well-earned reputation for running over budget, over time, or failing outright, because they ask a business to keep operating on an old system for a year or more while betting the entire future on a new one that has not yet been tested by real usage. This post covers the incremental strategy we use instead for most legacy PHP migrations, and the situations where a full rewrite is genuinely still the right call.

Why do full rewrites of legacy applications fail so often?

Because a full rewrite requires reaching feature parity with the old system before it can safely replace it, and legacy applications almost always have more functionality — including undocumented edge cases nobody remembers exists until it breaks — than a rewrite's initial scope accounts for. The business keeps running on the old system throughout, which is unglamorous parallel-running work that is easy to underinvest in, and the go-live moment concentrates all the risk of the entire migration into a single cutover event. If that cutover reveals a gap, the business is stuck between a half-abandoned old system and an incomplete new one, which is the specific failure mode that gives full rewrites their reputation.

What is the strangler fig pattern, and how does it actually work for PHP?

The strangler fig pattern, named after a vine that gradually grows around and eventually replaces its host tree, means incrementally replacing pieces of the legacy application behind a shared interface — usually a routing layer that decides, per request, whether the old or new codebase should handle it — rather than a single wholesale cutover. For a legacy PHP application, this typically means introducing a modern framework alongside the legacy code, routing new features and the highest-value existing pages through the new codebase first, and migrating remaining legacy pages incrementally as capacity allows, with both systems sharing the same database throughout the transition.

This lets you ship real, visible improvements from early in the process rather than asking the business to wait a year or more for any benefit, and it means a problem in the migration affects one migrated piece, not the entire system at once.

How do you decide what to migrate first?

Start with whichever part of the application is both highest-value to improve and lowest-risk to migrate — usually a feature that is actively painful in its current form (slow, buggy, hard to change) but does not touch the most business-critical, highest-traffic core of the system. This builds confidence in the migration approach and the new codebase's stability before tackling the parts of the application where a mistake would be most costly. We resist the instinct to migrate the most business-critical feature first just because it feels like the most important win — sequencing risk correctly matters more than sequencing impact first.

Does everything in the legacy application actually need to be modernised?

No, and this is worth saying directly because it runs against the instinct that "legacy" automatically means "bad." Code that works reliably, is not actively causing problems, and is not blocking new development is often better left alone than rewritten for the sake of consistency — every migration decision has an opportunity cost, and time spent modernising stable, low-risk code is time not spent on the parts of the system actually causing pain. We assess this explicitly during the technical audit rather than default to "rewrite everything eventually."

What does a technical audit before migration actually need to cover?

Code quality and structure, to understand how tightly coupled different parts of the application are — tightly coupled legacy code is harder to strangle incrementally and may push toward a more contained rewrite of that specific module. Dependency health, since outdated PHP versions or abandoned libraries carry security risk independent of the migration question. And critically, an inventory of business logic that is not documented anywhere except in the code itself — this is where the real risk in legacy migration usually lives, since a piece of logic nobody currently understands is exactly the piece most likely to be missed or broken during migration.

When is a full rewrite still the right call, despite the risks?

When the legacy codebase's architecture is so fundamentally mismatched to current requirements that incremental migration would mean carrying forward structural problems indefinitely — for example, a monolith with no meaningful separation between data access and business logic, where even introducing a strangler routing layer requires disproportionate effort relative to a from-scratch rebuild of that specific concern. It can also be the right call when the legacy technology itself (a very old PHP version with no supported upgrade path, or a framework no longer maintained at all) makes even running the old system alongside a new one impractical. We recommend a full rewrite only when the audit genuinely supports it, not as a default answer to "the code is old."

How do you handle the database during an incremental migration?

Ideally, both the legacy and new codebase share the same database throughout the transition, with schema changes made carefully and with backward compatibility in mind so the legacy code continues functioning against an evolving schema. This avoids the significant added complexity of keeping two databases in sync during the transition, which is a common and often underestimated source of bugs in incremental migrations that split the database prematurely.

Frequently asked questions

Placeholder image (portrait-1x1) — replace before launch — Vishal Kumar

About the author

Vishal Kumar

Senior Backend Developer

Senior Backend Developer experienced in building scalable APIs, robust backend systems, and high-performance applications. Focused on clean code, system reliability, and efficient solutions.

Related posts