#software-development
#product
Opinion

PHP 5.6 to PHP 8 or full rewrite: how to decide

Upgrading PHP 5.6 to PHP 8 usually costs less than a rewrite. Three measurements (compatibility, dead dependencies, revenue flows without tests) tell you if your case is the

Por Victhor Araújo

Founder of Revin. Engineer by training, specialist in software development and digital products.

Old code is usually the only living documentation of the rules your revenue depends on

Old code is usually the only living documentation of the rules your revenue depends on

You can take a PHP 5.6 system to PHP 8, and in most cases that reach my desk it costs far less than a rewrite. What changes the math is the second half of the question. Moving to PHP 8 and dropping Laravel on top of what already exists are two different jobs with two different risk profiles, and the second one is the one that eats a quarter.

Before you pick a side, measure three things, in this order: how many files PHP 8 actually breaks, how many of your direct dependencies nobody maintains anymore, and how much of your revenue runs through code you cannot execute end to end without a real customer on the other side. That is about two days of work for a developer who already knows the codebase. Cheap next to the price of getting this call wrong.

A Gala, a Granny Smith, and an orange

Half an hour of PHPCompatibility turns "feels like a lot" into a per-file count

Half an hour of PHPCompatibility turns "feels like a lot" into a per-file count

A client once asked me whether going from MySQL 5 to MySQL 7 was the same thing as moving to Postgres. He was not technical, so I answered with the fruit bowl. MySQL 5 is a Gala, MySQL 7 is a Granny Smith, Postgres is an orange. All three are fruit, all three are SQL databases, and nobody swaps an apple for an orange halfway through a recipe expecting the same cake.

PHP 5.6 to PHP 8 is swapping one apple for another. Same language, almost all the syntax your team wrote still parses, and the work sits in specific incompatibilities and in behavior that quietly changed. Putting Laravel inside a procedural system is the orange. The framework decides where the request enters, how routing resolves, how the database gets touched, and where business rules live. That is not an upgrade, it is replacing the foundation while the building stays occupied.

Both are doable. But whoever promises both on the same timeline usually delivers the first one and spends the rest of the year on the second.

Measurement one: how many files PHP 8 actually breaks

Stop arguing from vibes. Install PHPCompatibility on top of PHP_CodeSniffer, point it at your target version (8.2 or 8.3), and run it across the repo. Half an hour later you move from "feels like a lot" to a per-file count.

The result usually lands on the good side. In a mid-sized system the report flags somewhere in the low hundreds of occurrences, most of them clustered in a handful of files. Nearly all of it is mechanical: `each()` gone, `create_function` removed, `mysql_` calls that already died in 7, arguments passed by reference where that is no longer allowed. Rector clears a big slice of it on its own. What is left is a boring afternoon, not a project.

The danger lives in what does not break. Loose comparison between strings and numbers changed in PHP 8. On 5.6, `0 == "abc"` returned true. On 8, it returns false. Nothing crashes, no log lights up, the `if` just takes the other branch. If that branch decides a discount, a shipping tier, or an access check, your business rule changed and nobody signed off on it.

So the order matters. First write characterization tests over the flows that make money, freezing today's behavior even where it is odd. Then bump the version. Teams that do the reverse learn about the change from a support ticket three weeks later, on a wrong invoice total.

Measurement two: what you depend on that nobody maintains

On the middle path the router hands over one route at a time and the system runs split for a while

On the middle path the router hands over one route at a time and the system runs split for a while

Second measurement, half a day: `composer outdated --direct` and `composer audit`. Add the native extensions your server loads, because mcrypt left core back in 7.2 and homegrown crypto is still glued to it in plenty of shops.

This is where the decision flips. If your direct dependencies have PHP 8 compatible versions, even ones that need work, you have a short upgrade path. If the system is welded to a framework that stopped shipping fixes years ago, something is getting rewritten either way. The difference is that now you know it from a measurement, and you can rewrite the entry layer instead of the whole system. Rewriting routing is a task. Rewriting ten years of business rules is a different animal, and it is the one that kills timelines.

One more thing that shortens the boardroom debate: running PHP 5.6 today means running without security fixes since the end of 2018. That does not decide between upgrade and rewrite. It removes the third option, the one everyone secretly prefers, which is leaving it alone and revisiting next year.

Measurement three: how much revenue runs without a net

The third measurement is not test coverage. List the four or five flows that generate money (order capture, pricing, billing, invoicing, the integration with whoever pays you) and ask one question about each: can I run this end to end on a clean machine with no customer involved?

That answer decides more than any stack debate. A system whose money flows are reproducible survives a version migration fine. A system where the only way to know billing worked is to wait for tomorrow's statement survives neither path, because in both you are shipping blind.

And be careful with a pretty report. I have opened projects with high coverage where the tests called the method and asserted nothing, no `expect` anywhere. The metric existed, the guarantee did not. Same reasoning applies when you decide who does the migration: an embedded senior squad measures first and writes second, which is how we work on inherited code.

"Nobody understands that code anymore"

That line shows up in almost every thread on this topic. It is true, and it is the worst possible argument for a rewrite.

Ugly code is the living documentation of your rules. That strange `if` in the commission calculation is a deal someone closed with a partner in 2019 and never wrote down anywhere else. When a team rewrites from scratch, it does not rewrite what the code does. It rewrites what it managed to understand, which is always less. The remainder comes back as production bugs, one a week, for months.

There is also the line nobody puts on the slide. A rewrite freezes the product. While the team rebuilds what already exists, your competitor ships and your sales team keeps demoing the same screens. Given what engineering costs per hour, a quarter with no new customer-facing work is money you pay twice, once in payroll and once in the frozen roadmap.

The middle path, and the day it does not work

What works in most cases is three moves, in this order:

  1. Go to PHP 8 with the code exactly as ugly as it is, with characterization tests written first over the money flows. Cheapest step, and the one that takes you off the security risk.
  2. Tidy what exists without changing fruit: Composer with PSR-4 autoloading, a single front controller, config out of the code, and real logging. That is housekeeping, not a framework.
  3. Stand Laravel up beside it on the same database and let the router hand over one route at a time. New signup is born in Laravel, the old checkout stays put until tests can back the move.

During step three the system runs split, and that bothers every engineer who likes clean things. Sit with the discomfort. It is reversible on any given day, which a nine-month rewrite is not.

Now the limits, because advice that only asserts is worth nothing. This path fails in three situations. When a core dependency has no PHP 8 compatible version and the vendor is gone, you are already rewriting and just have not said it out loud. When the business changed so much that the system models a company that no longer exists, a version bump preserves the wrong model in newer syntax. And when the whole operation fits in eight screens, the cost of rebuilding drops to weeks and the math flips, though I would count those screens with the code open, because that number always grows.

If you are taking this to a meeting this week, ask for two numbers before you look at a single proposal: how many occurrences PHPCompatibility flags, and how many direct dependencies have no PHP 8 release. If nobody on your team can answer that in two days, your problem is not choosing between migration and rewrite. It is that nobody has opened the system to look yet.

Ready to elevate your business

Schedule a meeting
Share
Link de compartilhamento LinkedinLink de compartilhamento XLink de compartilhamento WhatsappLink de compartilhamento Facebook

Every two weeks. The technical decisions we made, and what we learned.