Modernizing a legacy PHP application without a rewrite
Big-bang rewrites usually run late and ship with fewer features. How we use the strangler fig pattern to modernize legacy PHP one route at a time.

Every few months a client arrives with a PHP application that has served the business for a decade and is now holding it back. It runs on an unsupported PHP version, mixes SQL with HTML, has no tests, and every change takes three times as long as it should. The instinct is to rewrite it from scratch in a modern framework. We almost always recommend against that. Rewrites of working systems run late because the old system's behavior is only fully documented in its code, and a rewrite has to rediscover every rule the hard way while the business keeps asking for new features. The approach that works far more reliably is incremental replacement.
The strangler fig pattern
The name comes from a plant that grows around a host tree until it can stand on its own. In software, you put a routing layer in front of the legacy application, build new functionality in a modern codebase behind the same layer, and move routes over one at a time. The old system keeps running and keeps earning money while it shrinks. Eventually nothing routes to it and it can be switched off.
The benefits are practical. Each migrated piece ships to production within weeks rather than months. Risk is limited to one area at a time. And if priorities change halfway, the business still has a working system, partly modernized, rather than a half-finished rewrite that delivers nothing.
Step one: stabilize before you move
Before migrating anything, we make the legacy system safe to work around:
- Put it in version control if it is not already, and set up a repeatable deploy.
- Upgrade PHP as far as possible without major code changes, fixing deprecations along the way. Getting from an end-of-life version to a supported one is often the most urgent security fix.
- Add error tracking and basic logging, so you learn what actually breaks.
- Write characterization tests for the most important journeys. These do not assert what the code should do; they record what it currently does, so you notice when a change alters behavior.
Characterization tests are often end-to-end or HTTP-level tests, because legacy code rarely has seams for unit testing. A test that posts a form and checks the resulting database rows is crude, but it is exactly the safety net you need.
Step two: put a router in front
The routing layer can be a reverse proxy, a CDN rule set or the new application itself acting as a front controller that forwards unknown routes to the old code. We usually start with the simplest option: the web server routes specific paths to the new application and everything else to the legacy one.
location ~ ^/(account|api/v2|checkout) {
proxy_pass http://new_app;
}
location / {
proxy_pass http://legacy_app;
}
Sessions and authentication are the first shared concern. Both systems need to recognize a logged-in user. We usually move session storage to Redis with a format both sides can read, or have the new application issue a token that the legacy code validates. It is worth getting this right early, because every migrated route depends on it.
The first route you migrate is mostly infrastructure work. The tenth is mostly feature work. Budget accordingly.
Step three: choose the order
Which routes to move first is a business decision as much as a technical one. We rank candidates on three axes: how often the area changes, how much pain it causes today, and how tangled it is with the rest of the system. The best early candidates change frequently, cause visible pain and have clear boundaries. An admin reporting screen or a customer account area often fits. The checkout or the pricing engine, which touches everything, usually goes later once the patterns are proven.
For each area, the cycle is the same: write or extend characterization tests, build the replacement in the new codebase, run both in parallel where possible to compare outputs, switch the route, watch the metrics, then delete the legacy code for that area.
Sharing the database
For most of the migration, the old and new applications will read and write the same database. That is fine and far simpler than keeping two databases in sync, but it needs rules:
- Every table has one owner at any time. Only the owning application runs migrations against it.
- The new application wraps legacy tables behind repositories or models so their quirks do not leak into new code.
- Schema changes follow expand and contract, so the legacy code keeps working while the new code evolves.
- Where the legacy system relies on triggers or implicit side effects, document them and replicate them explicitly in the new code before switching.
Knowing when you are done
The last ten percent of a strangler migration is where projects stall. The remaining legacy routes are rarely used and nobody is excited about them. We set an explicit end date and treat decommissioning as a deliverable: check access logs for any remaining traffic to the old application, migrate or retire what is left, archive the codebase and shut down the servers. Until the old system is switched off, you are paying to run and secure two applications.
A typical engagement for a mid-sized legacy application runs four to nine months, with the first migrated area in production within six weeks. For one B2B distributor, we moved an order portal off a 2012-era custom framework onto Laravel over seven months, with no downtime and no freeze on feature work. Monthly bug reports fell by roughly two thirds over the same period, mainly because the new code had tests.
Where we can help
Our legacy code modernization service follows this approach by default, and most of our PHP migrations land on Laravel and modern PHP because it lets existing team members keep contributing. Where a security review is overdue, we often combine the first phase with a security audit of the legacy code.
Modernize without stopping the business
If your application is too important to rewrite and too painful to leave alone, let us look at it. Send us a short description and we will come back with a fixed-price first phase within 24 hours. Get your quote.



