Leaving PHP, Part 9: rewriting incrementally is now realistic
Part 1 through Part 8 made the case that the arguments against PHP have accumulated. That still leaves the biggest counterargument standing: leaving is expensive. A working PHP codebase is years of business logic, edge cases, and bug fixes; throwing it away and rewriting from scratch is rarely the right call. Part 9 is the response to that specific counterargument.
The old argument was rewrite or stay
For a long time the honest answer to "should we leave PHP?" was "not until the pain is worse than a full rewrite". A ten-year-old Laravel monolith with 200,000 lines of business logic represents a very large amount of implicit knowledge, and re-implementing that knowledge from scratch in a new stack is a project of the size that eats teams. Half the horror stories about failed rewrites are about exactly this shape: a two-year effort that lands late, over budget, and missing the corner cases the old codebase had already dealt with.
That argument was correct, and it is still correct for the specific bet it addresses: do not rewrite a working codebase from scratch. The mistake was to conclude from that argument that leaving PHP means rewriting from scratch. It does not, and it has not for several years.
Strangler-fig behind a shared gateway
Martin Fowler's strangler-fig pattern is the oldest of the incremental options. Put an HTTP gateway in front of the PHP application. Route every request through the gateway. For new features, build the endpoint in the new stack and register it in the gateway; every other route continues to hit PHP unchanged. Over time the gateway routes an increasing share of traffic to the new services, and the PHP share shrinks. There is no cutover date. The old codebase stays alive as long as any traffic is routed at it.
The pattern's virtue is that risk stays bounded to whatever the new service touches. A misbehaving new endpoint fails in isolation. The PHP side keeps serving everything else while the incident is resolved. The pattern's cost is the shared state: sessions, authentication, the database. Anything the PHP side reads or writes has to also be reachable from the new stack, on the same schema, with compatible semantics. That is real work but it is bounded work, and it is done once per shared surface, not once per file.
File-by-file with a compatibility runtime
The newer option is transpilation. Instead of routing traffic to a new service, you transpile the PHP source to JavaScript, keep the same request lifecycle, and run the transpiled code against a compatibility runtime that reproduces the PHP semantics your application was written for. The runtime is what carries you across: PHP's arrays behave like PHP arrays, the reference semantics are intact, the standard library functions have the same signatures and edge cases. The transpiled output looks like the original code's shape, one file per file, and you can start with a single directory if you want to.
Because the target is JavaScript on Node (or Bun, or any modern runtime), you get the ecosystem benefits from day one: the type system if you convert to TypeScript, the async model if you enable it, the observability tooling, the deployment shape. You get them incrementally. A file that is still under active PHP development can keep being edited as PHP; its transpiled twin regenerates on save. A file that is stable can be adopted as JavaScript directly and edited from that side going forward.
The transpile path is not a substitute for the strangler-fig; the two compose. Transpile the whole application to get onto Node. Route new features through a native TypeScript service via the strangler-fig gateway. The old code keeps working through the compatibility runtime while you decide, per subsystem, whether to leave it transpiled or rewrite it natively.
Extract the hot paths, leave the rest
The third pattern is the pragmatic one: identify the endpoints that carry the traffic, extract those to the new stack, and leave everything else in PHP. The 80/20 shape holds in almost every real application. A typical e-commerce site has ten endpoints that serve 90% of the request volume: home, category, product, search, cart, checkout. The admin panel, the reporting screens, the CRUD for staff users, the background scripts: they run rarely and they do not need to be moved. Move the ten. Leave the rest.
This pattern is dramatically cheaper than either of the others because the migration budget is capped by the size of the extracted set, not the size of the codebase. It also decouples migration from replatforming: the extracted endpoints get the new stack's benefits (async, types, deployment shape) where they matter most, and the parts of the application that are fine as PHP simply stay as PHP. Not every application has to leave the language entirely. For many, the honest answer is "we moved the ten hot endpoints, we froze the rest, and we are done".
The economics have changed
The through-line across all three patterns is that migration is a gradient. There is a whole range of positions between "we run PHP" and "we run TypeScript", and most of them are viable production states. The strangler-fig sits at the outer end where new stuff is native and old stuff is PHP. The transpile path sits in the middle where the whole application runs on Node but retains PHP semantics. The hot-path extract sits at the near end where PHP is still the main platform and only the traffic-heavy endpoints have moved.
Each position on the gradient is cheaper than the big-bang rewrite. Each unlocks a real subset of the benefits the earlier posts made the case for. A team does not have to commit to the far end of the gradient to start seeing the payoff, and the migration budget scales with the distance chosen, not with the size of the codebase. That is the economic shift, and it is the reason the "leaving is too expensive" argument no longer closes the case.
Disclosure
Full disclosure. I am building Pext, a commercial PHP-to-JavaScript transpiler, precisely because I believe this gradient should be smoother than it currently is. Take this post with that grain of salt. The strangler-fig pattern predates Pext by a decade and is a legitimate option whether or not you use a transpiler. The hot-path extraction pattern is language-agnostic. The transpile path is the one Pext exists to sell. All three of them are cheaper than a rewrite from scratch, and the underlying point about migration being a gradient holds regardless of the specific tool you reach for.
Up next in the series
One post to go. Part 10 is the series finale and it steps back from the tactics. The individual arguments (hiring, runtime, types, ecosystem, type sharing, async, deployment, tooling, migration cost) are the tactical setup. The strategic frame is a different question: over the next ten years, which ecosystem compounds in your favour? That is the closing argument.
Curious what the transpile-path economics look like for your codebase specifically? Book a demo and we will walk you through it.