Rescuing a Legacy PHP Application: Stabilise First, Then Modernise

In this article
  1. Stabilise before anything else
  2. Get to a supported PHP version
  3. Add tests around what matters
  4. Modernise piece by piece
  5. Keep the database tidy
  6. What to expect

A PHP application that has run a business for ten years has usually earned its keep many times over. It has also usually outlived the team that built it, the PHP version it was written for, and the framework conventions of its time. The symptoms are familiar: deployments that nobody wants to do, a hosting provider warning about an unsupported PHP version, features that take months because every change breaks something unexpected. The application is too valuable to switch off and too fragile to extend. This is how we approach these projects, and why the right first step is almost never a rewrite.

Stabilise before anything else

The first sprint on an inherited application is not about new features and not about modernisation. It is about making the application safe to change. That means four things.

  • A reproducible environment. The application runs on a developer machine from a documented setup, with the same PHP version and extensions as production. Until this exists, every change is tested in production.
  • Version control that matches production. It is common to find that the deployed code differs from the repository, with hotfixes made directly on the server. We reconcile them so there is one source of truth.
  • A deployment procedure that is written down and, ideally, automated. One command, or one button, that deploys the current code and can roll it back.
  • Error logging and monitoring. Many legacy applications fail silently. A week of real error data shows where the actual problems are, which are rarely the ones people complain about.

These changes alter nothing the users see, and they make every later change faster and safer.

Get to a supported PHP version

Running an unsupported PHP version is a security problem and, increasingly, a hosting problem. The upgrade path from PHP 5 or early PHP 7 to a current release is well understood, and static analysis tools identify most incompatibilities before anything runs. The work is in the long tail: deprecated extensions, functions removed in later versions, and behaviour that changed without notice. We do this upgrade as its own project, with a full regression pass, before touching anything else, because every later step benefits from modern language features and tooling.

Add tests around what matters

A legacy application rarely has tests, and writing tests for all of it is neither possible nor useful. We write tests around the parts of the application that make money or carry risk: order processing, pricing, invoicing, permissions, data imports. These tests describe what the application does today, including its quirks, so that later changes can be checked against them. Characterisation tests of this kind are the safety net for everything that follows.

Modernise piece by piece

With the application stable, upgraded and partly tested, modernisation can begin, and the key is that it happens inside the running application rather than beside it. The approach depends on what we find.

Where the application has no framework, we introduce one at the edges: a modern router, a dependency container and an ORM, with new features written in the new structure and old features moved across when they need changing anyway. Laravel and Symfony both support this kind of gradual adoption, and we have used both.

Where the application already uses a framework but an old version, we follow the framework's upgrade path release by release, which is tedious but predictable, and we describe the Laravel version of this process in a separate article.

Where a part of the application is beyond repair, we replace that part only, behind the same URLs and the same database, so that users and integrations notice nothing.

Keep the database tidy

Legacy databases accumulate columns nobody uses, columns with two meanings, and constraints that exist only in application code. We document the schema as we go, add constraints where the data allows, and introduce migrations so that schema changes become code rather than manual SQL. We do not redesign the schema wholesale. Data migrations are the riskiest part of any modernisation, and they are done in small, reversible steps.

What to expect

The stabilisation phase typically takes two to four weeks. The PHP upgrade depends on the size of the codebase and the gap between versions, and the audit tells us before we commit. Modernisation after that runs in two-week sprints alongside feature work, which is the aim. The business keeps getting features while the foundations improve underneath them.

A full rewrite remains an option, and sometimes it is the right one, usually when the application is small or when the business has changed so much that the old one no longer describes it. We say which route we recommend after an audit, with reasons. Our PHP development, Laravel development and maintenance pages describe the work, and an audit is free to scope.

Working on Something Similar?

Tell us what you are building. Scoping is free, and we will say plainly what we would and would not do.

Get Free Consultation