Migrating a Legacy PHP App to Laravel 13: What We Found

TL;DR: We inherited an e-commerce platform built on a cracked, unlicensed copy of a PHP CMS — meaning it had gone over two years without a single official update — with a partial Angular frontend bolted on, and a codebase so heavily and carelessly modified that a full framework upgrade was already off the table before we even started. Finding the right place to make a one-line change routinely took an hour or two. Before we touched anything security-related, we commissioned an independent black-box and gray-box audit to get real evidence of how bad it actually was, fixed the fires that report told us to fix first, and we're now rebuilding it properly on Laravel 13. This is that story, including the parts that aren't finished yet.
What We Actually Inherited
The platform was built on a PHP e-commerce CMS, with part of the frontend layered on top in Angular — already an unusual combination, since the CMS had its own templating and rendering expectations that the Angular layer had to work around rather than with. That alone would have been manageable. What made it genuinely dangerous was underneath: the CMS license was a cracked, unofficial copy. Whoever set this up years earlier had never paid for it, which meant it had never received a single official update in over two years. No security patches, no bug fixes, nothing — frozen at whatever state it was pirated in.
On top of that, the application itself was running on Laravel 9 or 10, already behind, and the CMS's own core files had been directly edited rather than extended the way the framework intended. That combination — an unlicensed, unpatched CMS with a hand-modified core — meant a normal upgrade path didn't exist. You can't pull in official updates to a codebase that was never legitimately receiving them in the first place, and you can't cleanly upgrade code that's been hacked apart at the framework level.
The state of the actual code told its own story. Whoever had worked on it before us appeared to be inexperienced developers, and it showed in a very specific way: entire files duplicated wholesale, sometimes differing from their twin by a single changed line. Not reused, not abstracted — copy-pasted, edited slightly, and left as two (or more) parallel versions of nearly the same logic. That's the kind of thing that doesn't happen by accident; it happens when nobody trusts — or nobody has time to understand — the code that already exists, so it's easier to duplicate it and tweak it than to touch the original.
The Real Cost of a Mess Like This
Here's the number that actually mattered to the business, not just to us as engineers: once we took over, management would come to us asking for what should have been small feature changes, and it would routinely take an hour or two just to locate the right place in the codebase to make the modification — before writing a single line of the actual fix. Not because the change itself was hard. Because finding where it belonged, in a codebase full of near-duplicate files and framework internals that had been quietly rewritten, was genuinely that time-consuming.
That's the cost nobody puts in a project estimate: not the difficulty of the work, but the tax of navigating a codebase that actively resists being understood.
What We Did Instead of a Full Rewrite — At First
A ground-up rewrite was the honest answer from day one, and we said so. But a full rebuild through a third-party team was quoted at over a year, and the business needed the platform live and functioning in the meantime — it was already generating real revenue, and going dark for twelve-plus months while a rewrite happened wasn't a realistic option management could accept.
So as a first step, rather than continuing to patch directly inside the tangled CMS/Angular code, we built out module-wise service classes sitting on top of the existing framework — a dedicated service layer per business domain (orders, inventory, whatever the feature area was), so that new work and modifications went through clean, isolated, purpose-built code instead of threading further changes into the legacy internals. It didn't fix the underlying mess, but it meant every feature built from that point forward was addressing new, understandable code — and it stopped the problem from getting worse while a real rebuild was being planned.
The Security Problem Underneath All of It
An unlicensed CMS frozen for two-plus years without patches is already a real risk on its own. It got more concrete than that: the mobile app was authenticating against the API using session-based auth — the same mechanism meant for a browser holding a cookie, not a mobile client. That's a known-bad pattern for exactly the reasons you'd expect: no clean way to revoke a single device's access, no real token expiry model, and a much larger attack surface than a proper token-based scheme would have. Given everything else about the codebase, we assumed this wasn't the only issue like it — just the one we'd already spotted.
Getting Proof Before We Touched Anything
Rather than act purely on our own judgment of how bad things were, we told management: before any security fixes, get an independent security audit. We wanted a real, third-party black-box and gray-box assessment — tested the way an actual attacker would probe it, not just a code review from people already close to the project. That report became the actual prioritization list. It told us, with evidence instead of opinion, exactly which holes mattered most and needed to close first.
Once we had it, we fixed the urgent, dangerous findings as a contingency measure — not a rewrite, not a cleanup pass, just closing the specific doors that report said were open. That was the deal from the start: stabilize the immediate danger, buy the time a proper rebuild actually needs.
Where Things Stand Now
The critical fixes are done. The module-wise service layer is in place for everything built since we took over. And we're now in the process of doing exactly what we recommended on day one: rebuilding the platform from scratch, this time on Laravel 13, without the inherited license problem, without a decade of copy-pasted files, and without a mobile client authenticating like it's a browser tab. It isn't finished. I'd rather tell you that honestly than dress this up as a clean, completed success story — the real lesson here isn't the ending, it's what it actually takes to keep a business running safely while you fix something this deep.
Your Turn
Have you inherited a codebase you genuinely couldn't upgrade, no matter how much you wanted to? What was the actual dealbreaker for you — a cracked license, a core file nobody should have touched, something else entirely? Tell me about it in the comments.
Senior Full Stack Developer · Building SaaS products & teaching Laravel/React · 10+ years experience · Founder of Orion360 · Based in Dubai, UAE.
Was this post helpful?
Reviews & Ratings
Sign in to leave a review.



