PHP and legacy systems

Modernising legacy PHP without a big bang

Incremental modernisation separates risk reduction, runtime upgrades and structural improvement so that production does not depend on one all-or-nothing release.

Why complete replacement is attractive—and often dangerous

A mature PHP application can look irredeemably old. Unsupported runtime versions, custom libraries, mixed presentation and business logic, and years of exceptional cases make a clean rebuild appealing. The new architecture seems easier to reason about than the old one.

The difficulty is that production behaviour includes more than source code. Data corrections, exports, scheduled tasks, provider callbacks and manual workarounds form part of the real system. A full replacement must rediscover and reproduce all relevant behaviour before one go-live date.

A rewrite may still be justified, but it should be the result of evidence about system boundaries, business value and migration risk—not a reaction to untidy code.

Separate the reasons for modernisation

“Modernise the system” can mean several different things: move to a supported PHP runtime, remove a vulnerable dependency, make deployments repeatable, improve a slow workflow, add test coverage or replace an unmaintainable component. These objectives do not need the same solution or schedule.

A useful roadmap names the risk or outcome behind each item. Runtime and security work may be mandatory. Observability may be required before deeper change. A business feature may justify refactoring one boundary but not the entire application.

Typical drivers

  • Unsupported runtime or framework
  • Libraries with security or compatibility problems
  • Inability to test critical behaviour
  • Manual and fragile deployment
  • Performance or capacity limits
  • Business rules that cannot be changed safely
  • Integration contracts that are being retired

Build a safety net before changing structure

Legacy modernisation does not require perfect unit-test coverage. It does require evidence that critical behaviour still works. Characterisation tests, API examples, database snapshots, recorded result counts and deliberate manual acceptance cases can create an initial safety net.

The goal is to capture externally important behaviour, including inconvenient exceptional cases. Tests written only around the intended architecture may confirm the new design while missing what production users actually depend on.

Logs and deployment controls are part of the same safety net. A test environment cannot reproduce every production condition; post-release signals and rollback remain necessary.

Make the runtime upgrade a sequence

Jumping across several PHP versions together with a framework migration and dependency replacement makes failures difficult to attribute. A staged path keeps causes visible: inventory deprecations, update compatible libraries, remove runtime blockers, upgrade one boundary and verify business behaviour.

Static analysis and automated compatibility checks can locate syntax and API problems, but they do not prove that prices, permissions, imports or document generation remain correct. Technical checks and business acceptance must therefore be combined.

A staged runtime path
InventoryCompatibility fixesDependency updatesRuntime upgradeProduction checks

Each stage has its own verification and recovery point.

Create seams around high-risk components

A seam is a boundary through which old behaviour can be observed, tested or replaced. It may be a service interface around payment logic, a repository around a set of database queries, an adapter for an external provider or a command around a background task.

The purpose is not to introduce patterns everywhere. The boundary should isolate a concrete source of change or risk. Once callers use the seam, the internal implementation can evolve without forcing a system-wide migration.

Useful candidates for isolation

  • External APIs and provider-specific formats
  • Email, file and document generation
  • Payment and order-state transitions
  • Imports and exports
  • Authentication and permission checks
  • Large groups of repeated SQL queries

Treat database change as a production migration

The database often has the longest lifetime of any component. Schema cleanup that looks trivial in code can lock a large table, invalidate reports or break an external export. Data quality problems may also surface only when a new constraint is introduced.

Safer migrations separate additive and destructive work. New fields or tables can be introduced first, data can be backfilled in observable batches, readers can switch deliberately and old structures can be removed only after verification and a suitable retention period.

Every migration should define expected row counts, invalid-data handling, runtime impact and a recovery approach. A down migration is not always safe once business data has changed, so backups and forward corrections may be the real recovery mechanism.

Modernise by business capability

Technical layers are not always the best unit of delivery. Replacing every controller and then every database class can leave the business with months of internal change and no complete improved workflow. A bounded capability—such as product import, order export or user administration—creates a clearer acceptance boundary.

A vertical slice can include interface, domain logic, persistence, integration and monitoring for one use case. It demonstrates whether the target architecture works under real constraints before the same approach is repeated elsewhere.

Measure progress as reduced operational risk

Lines of rewritten code are a poor modernisation metric. More useful signals include supported runtime coverage, reproducible deployments, restore tests, reduced error volume, shorter recovery time, fewer manual corrections and the number of critical journeys protected by tests.

The roadmap should be revisited as evidence changes. A component expected to require replacement may prove stable behind a good boundary. Another area may reveal data corruption or provider dependence that deserves earlier attention.

Know when a replacement is still the right choice

Incremental work is not a rule that every legacy system must survive. Replacement becomes more plausible when the current application cannot run in a supportable environment, the data can be migrated with known semantics, the required behaviour is well understood and the old system can remain read-only during a safe transition.

Even then, migration, reconciliation, cutover and fallback need their own design. The distinction is between a planned replacement with explicit operating controls and an optimistic rewrite that assumes the hidden work will disappear.

Need a modernisation roadmap that protects operations?

A technical baseline can separate immediate risk, mandatory upgrades and structural improvement into controlled stages.

Discuss the modernisation path