Home/ Engineering Insights/ Legacy PHP Modernisation Roadmap

Legacy PHP Modernisation Roadmap: What to Do and in What Order

Most legacy PHP applications don't need a heroic rewrite. They need someone to take the fear out of them. The team knows the system works, mostly, but nobody wants to deploy on a Friday, the PHP version went out of support a few years ago, and the person who understood the billing code left in 2019.

The order you do things in matters more than any single technique. We've watched teams start with a framework migration before they had working backups or a single test, and a year later they were maintaining two half-finished systems. This post is the sequence we'd follow on an inherited PHP application, with the reasoning for each step. Where a step deserves its own write-up, there's a link.

What "legacy" actually means here

It isn't about age. A ten-year-old application with tests, a deploy script and a developer who knows it is in better shape than a two-year-old one built by a contractor who vanished. For our purposes, a system is legacy when changing it is expensive or risky: you can't deploy without crossing your fingers, the runtime is unsupported, there's no documentation, and fixing one thing tends to break another.

That definition matters because it tells you what the work is. You're not trying to make the code beautiful. You're trying to make change cheap and safe again.

Step 1: Take an inventory before you touch anything

Write down what you actually have. Not what you think you have. Most of this takes an afternoon:

  • PHP version and loaded extensions (php -v and php -m, run with the same configuration the web server uses)
  • The framework or CMS and its exact version, plus anything bundled into the codebase by hand
  • Dependencies: a composer.lock if there is one, otherwise a hunt through folders named lib, vendor, includes and third_party for copied libraries
  • Database engine, version and size
  • The server: operating system, web server, how PHP is run, who has access
  • How code gets to production (if the honest answer is "someone uploads files over FTP", write that down too)

Then the part people skip: everything that runs without a visitor. Look at the server's crontab, check for a CMS-level scheduler, and read the config for queue workers. Then look for outbound and inbound connections. Payment webhooks, email providers, SMS gateways, a nightly CSV pushed to someone's FTP server. These are the things that stop working quietly after a migration, and nobody remembers them until a customer complains.

A last cheap trick: take a month of web server access logs and count requests per path. The fifty busiest URLs are the application, as far as the business is concerned. Everything else is a candidate for later.

Step 2: Make it recoverable

Before any change, get to the point where you could rebuild the site from scratch on a clean server. That means:

  • Backups you've actually restored. A backup you've never restored is a hope, not a backup.
  • The code in version control. If it isn't, commit exactly what's on production as the first commit, ugly parts included. Then compare it against what a deploy would produce, because production often has edits that never made it anywhere else.
  • A staging copy. Same PHP version, same extensions, a copy of the database. Switch off outgoing email and use payment sandbox keys on it before you do anything else. Staging that sends real emails to real customers is a rite of passage nobody enjoys.
  • Somewhere errors go. At minimum, PHP's error log written to a file someone reads. Better, an error tracker that groups and alerts.
  • Secrets out of the web root and out of the repository.

If you skip this step, every later step carries risk you can't undo.

Step 3: Close the obvious security holes

You don't need to wait for modernisation to fix the dangerous things. Configuration files readable by everyone, a .git or .env folder inside the web root, SQL assembled by concatenating request values, upload handlers that let a script land in a folder PHP will execute. These are quick to find and worth fixing now. We go through the full checklist in our PHP security audit guide.

Don't confuse this with the PHP upgrade. Fixing the critical holes is a few days of focused work. Getting onto a supported PHP version is a project. Do the first one immediately and plan the second properly.

Step 4: Get onto a supported PHP version

This is usually the step that decides how the rest goes. Running an unsupported PHP version means no security fixes, and newer libraries stop supporting it, so your dependencies slowly freeze with it. Check php.net/supported-versions for the current dates, since they change every year.

Do it in stages, on staging first, and keep the change separate from any refactoring. The details, including the specific things that break between versions and how to find them, are in our PHP upgrade guide. It's also where estimates tend to slip, because the work depends on how many of your dependencies are abandoned, and you won't know that until you look.

Step 5: Add tests where you're about to change things

The aim isn't coverage numbers. It's a safety net around the parts you'll be touching. Start at the outside, with tests that load pages and submit forms and check what comes back, then work inward as you need to. The technique is called characterisation testing: you record what the code does today, odd behaviour included, and make sure it keeps doing it while you work. There's a full walkthrough in adding tests to legacy PHP code.

Step 6: Put modern foundations underneath

Once there's a safety net, introduce the tooling that makes the codebase easier to live with:

  • Composer. Even if the application is a pile of require statements, you can add Composer next to it, replace copied libraries with proper dependencies one at a time, and start autoloading new classes.
  • A coding standard and a formatter. Reformat the whole codebase in one dedicated commit, then add that commit to a .git-blame-ignore-revs file so git blame stays useful.
  • Static analysis. PHPStan or Psalm will find real bugs in old code. Both can generate a baseline file that records every existing error, so you can turn the tool on today, block new problems from being added, and shrink the baseline over time instead of fixing thousands of warnings up front.
  • A CI job that runs the tests, the linter and the analyser on every push.

Step 7: Pick a direction

Now, and only now, is it sensible to decide what the system should become. You've got a stable, recoverable, supported, tested application, and you know much more about it than when you started. The realistic options:

  • Stay where you are and keep tidying. This is a perfectly good answer more often than people admit.
  • Move to a framework gradually, route by route, while the old code keeps serving everything else. We cover how in running old and new PHP side by side.
  • Move to a newer version of the platform you're on, which is what most Magento 1 and PHPfox owners eventually face. See Magento 1 end of life and PHPfox to MetaFox.
  • Rebuild. Sometimes it's right. We lay out when, in rewrite or modernise.

If your templates are Smarty, there's a specific path for those too: modernising Smarty-template systems.

Write things down as you learn them

Every step above teaches you something about the system. Capture it while it's fresh: how to deploy, how to restore, what each cron job does, why that one odd condition exists. Our guide to documenting an undocumented codebase covers what's worth writing and what isn't.

Where projects go wrong

A few patterns repeat on almost every troubled modernisation:

  • Starting with the framework, before there are backups or tests.
  • Upgrading PHP directly on production, with no way to flip back.
  • Combining the upgrade, a redesign and a refactor into one release, so when something breaks nobody knows which change caused it.
  • Treating the whole thing as one big project with one big date. Funding it in slices that each leave the system better than before keeps it alive when priorities change.
  • Skipping the inventory and finding the nightly export job after it has failed for a week.

How long does it take?

It depends on size, test coverage, the number of integrations, and how far the PHP version has to travel. The inventory, backups and first security fixes are small jobs. The PHP upgrade is where estimates tend to slip. Everything after that is open-ended by nature, which is why we'd fund it in pieces rather than quote one number for the whole journey. Anyone who gives you a fixed price for "modernising" a system they haven't looked at is guessing.

Common questions

Should I upgrade PHP first or fix the code first?

Fix anything critical for security immediately, then get the application onto a supported PHP version, then do the refactoring. Refactoring on top of an unsupported runtime means doing the work twice.

Can I modernise without any downtime?

Usually, yes, if you work through a staging environment and change one thing per release. The cut-overs that cause downtime are the big-bang ones.

What's the cheapest useful first step?

The inventory and a tested backup. Both are cheap, and both make every later decision better informed.

Inherited a PHP system you're nervous to touch?

We'll start with an audit: what you have, what it depends on, what's at risk, and a sensible order of work.

See our legacy system support