Development

Legacy PHP Modernization: Refactor, Replatform, or Rewrite?

Legacy PHP modernization is not a binary choice between leaving the system alone and rewriting it. Teams often mix behavior-preserving refactoring, runtime and dependency upgrades, replatforming, and replacement work. Those labels describe different changes and should not be used interchangeably.

The correct choice depends on business change, operational risk, domain knowledge, and the shape of the existing system. “Old code” by itself is not a reason to rewrite working software.

Define the three strategies

Behavior-preserving refactoring changes internal design without intentionally changing externally observable behavior. The team adds tests, creates seams, separates responsibilities, and improves high-value areas over time.

Runtime and dependency upgrades make the existing application compatible with supported PHP, framework, library, and extension versions. They may enable later architecture work, but they are not automatically refactoring or replatforming.

Replatforming moves substantially the same application and business behavior onto a different operating platform, such as supported hosting, containers, or a managed database. It changes where the application runs more than what the capability does.

Incremental replacement implements a bounded capability anew and routes callers from the old implementation to the new one over time. A strangler pattern is one delivery approach for this work. It can replace a capability without replatforming the rest of the application.

A rewrite creates a replacement application and eventually cuts users over from the old one. Some code or data may be reused, but the delivery unit is a new system rather than a sequence of internal changes to the existing one.

These work types can overlap. A team may refactor a billing boundary, upgrade PHP, replatform the existing application, and replace a small reporting capability incrementally. Decide at the capability level before declaring one answer for the whole estate.

Start with evidence

Collect enough evidence to make the decision reviewable:

  • Business capabilities and their owners
  • Revenue-critical and compliance-sensitive flows
  • Production incidents and support volume
  • Change lead time and deployment failure rate
  • Runtime, framework, library, and extension support
  • Architecture and dependency map
  • Test and observability coverage
  • Data ownership, volume, and migration constraints
  • External integrations and extension contracts
  • Expected product changes over the next several years

Separate facts from interpretations. “PHP 7.4 is in production” is a fact. “The application cannot be upgraded” is a hypothesis until dependencies, code, and infrastructure have been examined.

A practical decision matrix

Assess each capability rather than scoring the application as one block:

Dominant constraintBest starting workImportant caution
Valuable behavior, but expensive internal changeBehavior-preserving refactoringProtect current behavior before moving boundaries
Unsupported PHP, framework, or dependencyRuntime and dependency upgradeKeep compatibility changes separate from optional redesign
Hosting or operating platform is endingReplatform the existing applicationProve behavior on the new platform before adding product change
One bounded capability blocks deliveryIncremental replacementDefine routing, data ownership, coexistence, and bridge removal
Product model is fundamentally differentRewrite the bounded systemBudget for requirement rediscovery and migration
Complex or regulated data must stay in placeRefactor or incremental replacementAvoid concentrating all migration risk into one cutover
Little domain knowledge existsLearn in place firstA broad rewrite has the highest hidden-requirement risk

Do not turn the table into an automatic score. A nonnegotiable constraint, such as a vendor shutdown or certification deadline, can outweigh several softer factors.

Consider a hypothetical reporting capability in a PHP 7.4 membership application. The reports are slow because one controller mixes access checks, query construction, and CSV output, while the host will stop supporting PHP 7.4. Preserve report behavior with characterization tests, refactor the query boundary, and upgrade the existing application runtime first. Replace only the report generator behind the same authorization contract if measurements show that capability still cannot meet its workload. The constraints lead to staged work; “legacy” alone does not lead to a rewrite.

When incremental refactoring fits

Refactor when the product behavior remains valuable, users depend on many small details, and the application can still be deployed and observed. It works especially well when the main pain is change cost rather than a fundamental product mismatch.

A useful sequence is:

  1. Make the environment reproducible.
  2. Protect critical flows with tests and monitoring.
  3. Upgrade unsupported runtime and dependencies in stages.
  4. Create boundaries around volatile or high-risk capabilities.
  5. Move business rules out of controllers, templates, and globals.
  6. Repeat where product work creates demand.

The risk is endless cleanup without business outcomes. Tie each slice to faster delivery, a removed operational risk, or a needed product capability.

When replatforming fits

Replatform when the operating environment is the constraint and the application's business behavior remains valuable. Examples include moving the same PHP application from an unsupported server image to maintained containers, or moving its existing InnoDB schema to a compatible managed database.

Treat PHP and dependency compatibility as its own tested workstream even when it is required by the destination. First prove the application on the supported runtime, then prove the platform move. Combining a runtime jump, infrastructure change, and capability rewrite makes failures difficult to attribute.

When incremental replacement fits

Replace a capability incrementally when one bounded area cannot meet product or operational needs but the rest of the application should remain. Examples include moving media processing behind a managed-service adapter, extracting search from an overloaded database, or routing new account workflows to a supported implementation while stable areas remain in place.

Martin Fowler's Strangler Fig Application describes gradual replacement around the old system. The technique needs more than a proxy. You need ownership boundaries, data synchronization rules, observability across both paths, and a way to reverse routing.

A reversible capability move has:

  • A clear entry point that can be routed
  • A contract between old and new implementations
  • A source-of-truth decision for every piece of data
  • Idempotent synchronization or event handling
  • Comparison metrics for business outcomes
  • A rollback path that does not lose accepted writes

The risk is a permanent distributed half-migration. Set exit criteria for each transitional bridge and assign an owner to remove it.

When a rewrite is justified

A rewrite can be reasonable when the current product model is no longer the product you need, the system is small enough to understand, or an external platform is disappearing and coexistence is impossible.

It is also plausible for a bounded capability whose behavior is simple, well specified, and independently deployable. Rewriting an isolated report generator is not the same risk as replacing billing, identity, and entitlement together.

Before approving a rewrite, answer:

  • Which current behaviors will be preserved, changed, or removed?
  • How will hidden requirements be discovered?
  • What is the first production-usable slice?
  • How will data be migrated and reconciled?
  • Can old and new run in parallel safely?
  • What stops feature parity from becoming an endless list?
  • What happens if the replacement is late?

The biggest rewrite risk is not syntax. It is rebuilding years of learned business behavior without realizing which details matter.

Keep runtime upgrades separate from architecture

An unsupported PHP version is an urgent risk, but it does not automatically require a rewrite. Often the safest sequence is to make the existing application run on a supported version before changing its architecture.

Why separate them?

  • Compatibility failures are easier to diagnose.
  • Production rollback remains simpler.
  • The team can use current tools and dependencies during later work.
  • Security risk starts falling before the full modernization is complete.

Likewise, do not use a runtime upgrade as cover for optional renames and abstractions. Make compatibility changes observable, then improve design.

Plan by outcomes, not universal timelines

No honest modernization plan can promise that every legacy application takes a fixed number of weeks. Size, coupling, testability, data, integrations, regulation, and team knowledge vary too much.

Use measurable outcomes instead, such as critical-flow smoke coverage, production on a maintained PHP version, one stable contract for the selected capability, a removed transitional bridge, or improved recovery and change lead time.

Estimate after a bounded discovery and tracer slice. Update forecasts as uncertainty becomes evidence.

Make every path reversible

Whatever work type you choose, prefer backward-compatible database changes, controlled routing, visible implementation markers, and rehearsed restoration. Keep old readers working during data transitions and do not delete old data at cutover until reconciliation and the observation window are complete.

A rewrite with one irreversible launch day carries concentrated risk. An incremental program can still be risky if every slice changes data in ways the previous release cannot read.

Choose the smallest strategy that changes the outcome

Use refactoring when behavior is worth keeping and internal structure is the constraint. Upgrade runtimes and dependencies when support is the constraint. Replatform when substantially the same application must run somewhere new. Replace a capability incrementally when it has a clean routing boundary. Rewrite only when the needed product is genuinely different or a bounded system can be replaced with manageable discovery and migration risk.

The decision can change as the team learns. Start with one production-relevant slice that tests the central assumption behind the strategy. Evidence from that slice is more valuable than a confident architecture diagram built before anyone touches the system.