ix
ix

informatix

PL version Book a discovery call
Home / Blog / PHP 7.4 to 8.x Migration — Breaking Changes Version by Versi...
• 8 min read •

PHP 7.4 to 8.x Migration — Breaking Changes Version by Version [2026]

Four generations of changes between 7.4 and 8.4, each with its own pitfalls: exceptions instead of warnings, null deprecations, dynamic properties, typed constants. The changes broken down version by version, a risk table and a step-by-step plan.

PHP 7.4 to 8.x Migration — Breaking Changes Version by Version [2026]
DevOps & maintenance

Migrating PHP 7.4 to 8.x is the most common migration in today's projects — most applications written between 2019 and 2022 run on 7.4. The good news: it is also the best-documented and most automated migration. The bad news: four generations of changes accumulated between 7.4 and 8.4, each with its own list of pitfalls. This article breaks the transition down version by version — what exactly breaks, how to fix it and in what order to proceed.

Key takeaway: The order is fixed: 7.4 → 8.0 → 8.1 → 8.2 → 8.3 → 8.4. You do not have to deploy every step to production, but you must pass through each one at the code and test level. Rector walks you through all the transitions automatically (withPhpSets(php84: true)), and the manual fixes focus on behavior changes, not syntax.

Why go step by step instead of jumping

Technically it is possible to rewrite code from 7.4 straight to 8.4 — Rector has a cumulative set. In practice a four-version jump has two drawbacks.

  • First: error diagnosis. When tests break after a mass change, you do not know which behavior is responsible. With version-by-version steps every commit corresponds to one transition — you find the bug in minutes, not days.

  • Second: dependencies. Frameworks and libraries have their own version requirements. Laravel 8 supports PHP 8.0, Laravel 9 — 8.0/8.1, Laravel 10 — 8.1/8.2, Laravel 11 — 8.2/8.3. Going step by step, you upgrade the framework alongside PHP, version by version — like stairs. A jump at once means upgrading the framework across four versions in a single step, which is a separate, large project.

In small projects (a dozen thousand lines, framework already supporting 8.4) a direct jump makes sense — Rector and tests are enough. In medium and large ones — steps.

7.4 → 8.0 — the biggest batch of changes

This is a generational transition: PHP 8.0 changed the error model, the type system and removed constructs from 5.x/7.0. The most important changes from the point of view of a typical project.

Exceptions instead of warnings. Many functions that returned false and logged a warning in 7.4 throw exceptions in 8.0 (ValueError, TypeError). This covers strpos() with invalid arguments, arithmetic functions, json_decode() with depth issues. Code that "worked despite warnings" stops working.

// 7.4: returns false + warning
$pos = strpos('abc', '');

// 8.0: throws ValueError — validate the arguments
if ($needle === '') {
    // handle the edge case
}

Stricter string-to-number comparisons. 0 == "abc" was true in 7.4 and is false in 8.0. This change silently breaks condition logic in old code — tests on decision paths (permissions, carts, discounts) are mandatory here.

Removed functions and constructs. each() (deprecated for years), create_function(), money_format(), the mysql_* extension (removed back in 7.0), the old preg_replace /e modifier. Rector replaces most of them automatically.

Changes in sorting and array functions. usort() and the sorting family — since 8.0, sorts are stable, which changes the order of equal elements. If code relied on instability (and some old code did), results will change.

Constructor promotion, attributes, named arguments — new possibilities that Rector partially introduces automatically (e.g. promotion), but you do not have to use them. A migration is not a refactoring to a new style.

8.0 → 8.1 — null deprecations and new types

A gentler transition, but with one change that touches nearly every project.

Deprecation of null in non-nullable parameters. Passing null to internal functions expecting a string/int (e.g. strlen(null), htmlspecialchars(null)) is deprecated since 8.1. The code keeps working but clutters the logs — and in future versions it will be an error.

// 7.4/8.0: works without a warning
htmlspecialchars($value); // $value may be null

// 8.1+: deprecation — fix it explicitly
htmlspecialchars($value ?? '');

New features worth using along the way: enums (enum Status: string), readonly properties, never as a return type, fibers (for async libraries), array_is_list().

Behavior changes: $GLOBALS works differently (full write access), some warnings about optional parameters before required ones became deprecations. Rector has rules for most cases.

8.1 → 8.2 — dynamic properties and readonly classes

Another "deprecation-focused" transition — code keeps working, but the logs fill up.

Deprecation of dynamic properties. Creating properties on the fly ($obj->newField = 1 on an object that did not declare the field) is deprecated since 8.2. This affects old ORMs, DTO objects and code that "attached" fields to models. Solutions: declare the fields, use #[AllowDynamicProperties] as a buffer, or refactor to arrays/types.

class UserDto
{
    // 8.2+ requires an explicit declaration or the attribute
    public string $name;
    public ?string $email = null;
}

Deprecation of part of string interpolation. Constructs like "${var}" (other than {$var}) are deprecated — a simple syntax swap Rector does automatically.

New features: readonly classes, DNF types ((A&B)|C), removal of some dynamic properties from the standard library. Full 8.2 support ships in Laravel 10/11 and Symfony 6.2+.

8.2 → 8.3 and 8.3 → 8.4 — cosmetics and wrap-up

The last two steps are the lightest — for most projects that is days of work, not weeks.

8.3: typed class constants (code overriding constants with a changed type gets an error — mostly libraries), get_class() without an argument deprecated (swap to static::class), minor Random\Randomizer changes, the new json_validate() function.

8.4: implicitly nullable parameters deprecated (string $x = null → ?string $x = null), property hooks (getters/setters on properties), asymmetric visibility, new array functions (array_find, array_any, array_all).

After reaching 8.4 the project is on a version with full support until the end of 2028 — and ready for 8.5 (another cosmetic transition when needed).

Summary table — what to check at every step

Transition Main risk Typical fix Time for a medium project
7.4 → 8.0 exceptions instead of warnings, string comparisons argument validation, condition fixes 1–3 weeks
8.0 → 8.1 null deprecations ?? '' and explicit nullable types 2–5 days
8.1 → 8.2 dynamic properties field declarations / attribute 3–7 days
8.2 → 8.3 typed constants (libraries) dependency updates 1–3 days
8.3 → 8.4 implicitly nullable parameters Rector + signature review 1–2 days

A practical 7.4 → 8.4 plan

The order of work that works in projects.

  1. Audit against 8.4 — PHPCompatibility with testVersion 8.4- and PHPStan show the full blocker list for all transitions at once. You then order the list by steps.
  2. Critical-path tests — do not start without them. 15–20 end-to-end tests are enough to begin.
  3. Rector in steps — first withPhpSets(php80: true), commit, tests. Then php81, php82, php83, php84. Each step is a separate commit and a separate test run.
  4. Framework in parallel — upgrade Laravel/Symfony one version per PHP step (the framework-to-PHP version map is in the framework documentation).
  5. Staging after every two steps — deploy to staging and walk the manual tests; update production when the whole chain is green.
  6. Deprecations to zero — after each step, run the application and clear the warnings. Leftover 8.1/8.2 deprecations are the most common cause of surprises at later steps.

The technical workflow with Rector — configuration, modes, CI — is in the article on the Rector PHP upgrade guide. The full process with audit, staging and deployment is described in the PHP migration guide. If you are starting from an even older version (5.6), there is an extra step through 7.x — covered in the same guide.

Frequently asked questions

Can I jump from PHP 7.4 straight to 8.4?

Technically yes — Rector has a cumulative set (withPhpSets(php84: true)) that applies the rules of all transitions. In practice, for medium and large projects it is better to go step by step: regressions are easier to attribute to a specific change, and the framework is upgraded in line with its version requirements.

What breaks most often on the 7.4 → 8.0 transition?

  • Three things: exceptions where there used to be warnings (string and arithmetic functions), changed string-to-number comparisons (0 == "abc" stopped being true), and removed functions (each(), create_function(), preg_replace with /e). The first two need logic review; the third is mostly automatic.

How long does a PHP 7.4 → 8.4 migration take?

  • Small project: 1–2 weeks. Medium system with a framework: 4–10 weeks (including framework and library upgrades). Large untested system: 2–4 months in stages. Rector performs the code refactoring itself in hours — the rest is tests, manual fixes and deployments.

Do I have to upgrade the framework right after the PHP migration?

Not "right after", but in the same journey. Frameworks have their own PHP support windows — e.g. Laravel 8 runs on PHP 8.0 but not on 8.2+. If you stay on an old framework with new PHP, you lose framework support. The best rhythm: at every PHP step, raise the framework by the version it supports.

Are dynamic properties a blocker for migrating to 8.2?

No — it is a deprecation, the code keeps working. But it clutters the logs and will become an error in a future version. The most common sources are old ORMs and code "attaching" fields to objects. The fix: explicit field declarations or #[AllowDynamicProperties] as a conscious buffer with a review date.

Will migrating from 7.4 affect application performance?

  • Usually positively: PHP 8.x is faster than 7.4 by tens of percent depending on the profile (JIT, engine optimizations, faster functions). CPU-bound applications see the biggest gain; database-bound applications less. The full effect also requires framework and library upgrades.

Sources

KS

Krzysztof Słonina

Full stack developer and owner of Inoventis Solutions (informatix). Since 2009 he has built Laravel/PHP web applications, e-commerce integrations and process automations — including OneCRMApp and FamilyTimelineApp, both running on Laravel, React and Inertia.js.

About the author
Free PDF

Web app requirements checklist

12 control questions to answer before talking to a vendor — faster estimates, fewer hidden costs.

Related posts

20.10.2026

PHP Migration & Upgrade Guide — 5.6/7.x to 8.x Action Plan [2026]

A PHP migration to 8.x removes security risks and speeds the application up without touching business code. The step-by-step path — from compatibility audit and tests, through Rector, to monitored deployment. With a support-date table, costs and a checklist.

Read more

23.10.2026

Rector PHP Upgrade Guide — Configuration and Recipes [2026]

Rector rewrites PHP code for a newer version automatically — removing deprecated functions, updating syntax and modernizing constructs. Configuration, a dry-run workflow, CI integration and the pitfalls to know before the first run.

Read more

23.10.2026

PHP Migration Checklist — 30 Points Before Deployment [2026]

30 items across five phases — from dependency inventory to post-deployment monitoring — with an acceptance criterion per phase and a list of the mistakes such a check eliminates. A migration plan template and a contractor inspection tool.

Read more