29 August 2026
11 min read

PHP 7.4 to PHP 8.4: what this upgrade really involves

By Tim Rutte, Cloud & Software Architect

Desk with a monitor listing errors and warnings from a compatibility run against PHP 8.4, next to a notebook with a ticked-off checklist

PHP 7.4 has had no security updates since November 2022. If your application still runs on it today, that is more than three years without patches. This is not a reproach. I see systems like this all the time. They run, they earn money, and nobody touches them voluntarily.

At some point the upgrade stops being postponable. The hosting provider retires the old PHP version. An audit flags the end-of-life version. A Composer dependency can no longer be updated because it requires PHP 8. Or a new developer simply refuses to work with a 2019 stack.

This article answers the question that comes before every one of these projects: what does a jump from PHP 7.4 to PHP 8.4 actually involve? Not as a marketing text, but as a field report from projects where I carried out exactly these migrations.

Why PHP 8.4 as the target

Some context first. PHP 8.4 was released in November 2024 and receives security updates until the end of 2028. Migrating today buys you several quiet years. PHP 8.5 is already out, but for a legacy upgrade 8.4 is the pragmatic target in my view: the ecosystem has settled, the relevant frameworks and libraries support it reliably, and the deprecations are known and documented.

And yes, there is a performance bonus. Depending on the workload, applications run noticeably faster on PHP 8.x than on 7.4, often in the range of 10 to 20 per cent, without you touching a line of code. That is a pleasant side effect. It is not the actual reason for the upgrade. The actual reason is that you are sitting on a version without security updates.

Five major versions in one jump

From 7.4 to 8.4 you skip five releases: 8.0, 8.1, 8.2, 8.3 and 8.4. Each brings its own breaking changes and deprecations. That sounds more dramatic than it is. In practice the problems are distributed very unevenly. A handful of changes cause 80 per cent of the work. I know those by heart by now.

PHP 8.0: the hardest step

PHP 8.0 is the break that hurts. Three things hit legacy code almost every time.

First: internal PHP functions now throw a TypeError for wrong arguments instead of a warning. Code that previously carried on with a warning in the log now fails hard. That is good for code quality and bad for the first deployment attempt.

Second: the comparison between strings and numbers changed. 0 == "foo" was true in PHP 7.4. In PHP 8 it is false. This is the most dangerous change in the whole upgrade, because it produces no error. The code keeps running, but an if branch suddenly behaves differently. More on that below.

Third: old constructs are gone for good. each(), create_function(), accessing string characters with curly braces such as $str{0}. In codebases that started before 2015 I regularly find dozens of these.

PHP 8.1: the flood of deprecations

PHP 8.1 has an unremarkable change that floods the logs in legacy projects: passing null to non-nullable parameters of internal functions is deprecated. It sounds academic. In practice it means that every htmlspecialchars($row['name']) where the database value can be null now produces a deprecation notice. In a typical shop or CMS that is thousands of places very quickly. The code still runs. In a future PHP version it becomes an error.

PHP 8.2: dynamic properties

PHP 8.2 deprecates dynamic properties, that is, the pattern where an object is simply assigned a property at runtime that was never declared in the class. Old PHP code lives on this. ORMs, home-grown data containers, configuration objects. There is the #[AllowDynamicProperties] attribute as a transitional measure, but that is a plaster, not a fix. On top of that, utf8_encode() and utf8_decode() are deprecated, and old code likes to misuse them as a supposed cure-all for encoding problems.

PHP 8.3 and 8.4: the quieter part

For migrations, PHP 8.3 is the most relaxed step. PHP 8.4 then brings one more change with broad reach: implicitly nullable parameters are deprecated. A signature such as function save(string $name = null) was standard for years and now has to read ?string $name = null. That is mechanically easy to fix, but it touches many files at once.

What PHP 8.0 to 8.4 add in new features, enums, readonly properties, property hooks, match, constructor promotion, I deliberately leave out here. For the upgrade itself, new features are irrelevant. They are the reward afterwards, not the task beforehand.

The three classes of problem, and why only one is dangerous

When I assess an upgrade project, I sort every problem into three classes.

The first class is fatal errors. Removed functions, removed syntax, type errors. This class is loud and therefore harmless. Static analysis finds them before deployment, and whatever slips through fails immediately and visibly. An error that shows up straight away is a cheap error.

The second class is deprecations. They are loud too, only in the log rather than in the browser. They do not force immediate action, but ignoring them merely defers the problem to the next upgrade. I fix them as part of the migration, because the tools for it are running anyway.

The third class is silent behaviour changes. That is the dangerous one. The string-to-number comparison from PHP 8.0 is the textbook case. No error, no warning, no log entry. Just a comparison that now comes out differently than before. If that comparison sits in a price calculation, a permission check or a voucher check, you have a problem that only the customer will find.

No static analysis reliably protects you against the third class. Tests do. And that is where the real risk of most legacy projects lies: not in the old PHP, but in the missing test coverage.

How I run an upgrade like this

The sequence is similar in every project, even if the weighting varies.

Step 1: inventory

Before I touch code, I want to know what I am dealing with. Which PHP extensions are installed and actually used? Which Composer packages block PHP 8.4? A composer why-not php 8.4 answers that in seconds and produces the list of dependencies that have to be updated or replaced first. On top of that comes the question of the framework, more on that shortly.

At the end of the inventory there is an honest list: these are the blockers, these are the known problem areas, this is the estimated effort. Only after that can anyone talk seriously about timeline and budget. Anyone who quotes you a fixed price before doing this inventory is guessing.

Step 2: static analysis and automated rewrites

The mechanical part of the migration can largely be automated today. PHPStan with the PHPCompatibility rule set finds removed functions, changed signatures and most deprecations. Rector rewrites a substantial share of the necessary changes directly: nullable signatures, removed syntax, trivial replacements.

The order matters. First the analysis with a baseline, so the existing background noise is documented and new problems become visible. Then automated rewrites in small steps that can be reviewed individually. A Rector run that changes 800 files in one commit is not brave, it is unreviewable.

In these migrations, automation realistically handles 60 to 70 per cent of the code changes. The rest is manual work in exactly those places where the code makes assumptions no tool understands.

Step 3: tests where it hurts

Very few legacy projects have test coverage worthy of the name. Retrofitting a full test suite is neither affordable nor necessary as part of an upgrade. What I do instead: characterization tests for the critical paths. Price calculation, checkout, login, exports, interfaces. These tests document the current behaviour under PHP 7.4 and then run under PHP 8.4 against the same expectations.

That is the only reliable weapon against the silent behaviour changes. And these tests stay in the repository after the project. They are the part of the budget that keeps returning value after the upgrade.

Step 4: step by step or straight across?

The most common technical question: do we migrate through 8.0, 8.1, 8.2 one at a time, or straight to 8.4? My answer is unspectacular: the code is brought to 8.4 compatibility in one go, but checked against several versions. During the migration the CI pipeline runs against PHP 7.4 and PHP 8.4 in parallel. That way the application stays deployable on the old version at all times while the new one is being prepared.

Deploying separately through every intermediate version costs time and gains almost nothing. The intermediate versions are checkpoints, not milestones.

Step 5: deployment with a way back

With clean preparation, the deployment itself is the most boring part, and that is exactly how it should be. Build the new PHP 8.4 environment in parallel, switch traffic across, watch the error rate and the logs. And above all: have a tested way back. The old 7.4 environment stays up until the new one has proven itself in real operation. If something unexpected turns up, you switch back, in minutes, not in a frantic overnight session.

Technically that is a blue/green deployment, and I treat the way back as part of the delivery rather than as an emergency plan. Not because I expect to need it, but because an upgrade without a defined way back is simply not a professional way to work.

The framework is often the real project

One point that proposals like to leave out: a pure PHP upgrade and a framework upgrade are two different projects that often coincide.

Anyone stuck on PHP 7.4 is usually stuck on an old framework version too. A Symfony 4, a TYPO3 9 or 10, a Shopware 5, an old Zend Framework. Those versions do not support PHP 8.4. Which means: before the PHP version can rise, the framework has to reach a state that carries the target version. With Symfony there is a defined upgrade path across the LTS versions. With TYPO3 likewise, with the well-known major jumps. With Shopware 5 it is honestly a platform migration, not an upgrade. For Zend Framework 1 there is no official path left, and there I work with compatibility shims or a gradual replacement, depending on the case.

This is exactly where the difference in effort between projects comes from. The PHP jump itself is predictable. How much framework legacy has to be dragged along decides between weeks and months.

What does it cost, realistically

Concrete numbers without an inventory would be dishonest, but I can name the cost drivers. They are not the ones you would intuitively expect.

The size of the codebase is surprisingly secondary. 500,000 lines of clean, tested code migrate faster than 80,000 lines without tests and with global state. What really drives the effort: missing test coverage, the volume of dynamic properties and untyped data flows, dead dependencies without PHP 8 support, home-grown frameworks, and whether the deployment is reproducible or lives on a server that somebody last touched in 2021.

As a rough orientation from my projects: a mid-sized application with a Composer setup and a reasonably current framework is a project of a few weeks. Add a framework major upgrade and we are talking about one to three months. Home-grown systems without tests and without package management are individual cases, and that is exactly where the inventory pays off as a small first engagement of its own: a manageable budget, and afterwards both sides know what they are talking about.

Common questions, answered briefly

Does the application have to go offline during the migration? No. The migration happens alongside live operation. The switchover itself is a short, controlled step with a way back.

Can we keep developing during the migration? Yes, with discipline. The migration changes go into the main branch in small steps, not as a parallel branch running for months. A big bang branch meant to be merged after three months is the most reliable way to make a project like this fail.

What about PHP 8.5 or later versions? If you have made the jump to 8.4 properly, meaning with static analysis, CI and tests on the critical paths, future version jumps are routine maintenance rather than a project. That is the real value of the migration: not the new version number, but the fact that the next one no longer frightens anyone.

Would PHP 8.1 or 8.2 not do for now? Technically yes, strategically it is almost always a mistake. PHP 8.1 already receives no security updates, and support for 8.2 ends at the close of 2026. If you are investing the migration effort now, aim for a version that will be carried for years.

Conclusion

An upgrade from PHP 7.4 to 8.4 is not black magic, but it is not an afternoon of search and replace either. It is a predictable project with known pitfalls: the hard breaks from PHP 8.0, the waves of deprecations from 8.1 and 8.2, the silent behaviour changes that only tests protect you against, and the framework as the secret lead actor.

If you run an application on PHP 7.4 and want to know what the jump means in your specific case, the first step is not a request for a quote but an inventory. I offer that as a compact fixed-price package, with an honest assessment of blockers, effort and risks. After that you decide on the basis of facts rather than gut feeling.

The inventory for your application

The assessment from step 1 is available as a fixed-price package: static analysis against the target version, a dependency check, a list of the breaks with location, severity and effort, plus an order of work whose steps can go live one at a time.

PHP upgrade