PHP 8 Migration Guide: How to Upgrade Legacy PHP Safely
Back to Blog
125 min read

PHP 8 Migration Guide: How to Upgrade Legacy PHP Safely in 2026

PHP 8 migration means moving a production codebase from PHP 7.x or an early 8.x release to a supported 8.x version, today PHP 8.4, without breaking what works. The deadline is close: PHP 8.2 loses security support on December 31, 2026. And on October 21, 2026, the PHP2Shell vulnerabilities showed what running an unsupported version actually costs.

This PHP migration guide is for CTOs, engineering directors and tech leads with production on PHP 7.4-8.2. It covers which versions are still supported, what PHP2Shell changes, the breaking changes you’ll hit, the 8-step process our team uses on Docker with Rector and PHPStan, where AI helps, and when an upgrade turns into a modernization project.

Key takeaways

  • Target version: PHP 8.4. It has security support until December 31, 2028, and wider library support than 8.5.
  • Deadline: PHP 8.2 reaches end of life on December 31, 2026. PHP 8.1 and older are already unsupported.
  • Risk: after PHP2Shell (CVE-2026-109618, CVE-2026-109619), PHP 7.4-8.1 received only source-level backports, and PHP 5.x-7.3 received no fixes from the PHP project at all.
  • Effort: most of the mechanical work is automated by Rector; testing and dependency upgrades take most of the time.
  • Duration: from a few weeks for a small, well-tested app to several months for a large legacy codebase. See the timeline section below.

Which PHP Versions Are Supported in 2026?

As of October 23, 2026, the PHP project supports four branches: 8.5 and 8.4 get full support, 8.3 and 8.2 get security fixes only. PHP 8.2 reaches end of life on December 31, 2026. Every version from 8.1 down, including PHP 7.4, no longer receives official releases.

Version

Released

Active support until

Security support until

Status (Oct 26, 2026)

PHP 8.5

Nov 20, 2025

Dec 31, 2027

Dec 31, 2029

Full support

PHP 8.4

Nov 21, 2024

Dec 31, 2026

Dec 31, 2028

Full support

PHP 8.3

Nov 23, 2023

Dec 31, 2025

Dec 31, 2027

Security fixes only

PHP 8.2

Dec 8, 2022

Dec 31, 2024

Dec 31, 2026

Security fixes only

PHP 8.1

NA

NA

Ended Dec 31, 2025

Unsupported

PHP 8.0

NA

NA

Ended Nov 26, 2023

Unsupported

PHP 7.4

NA

NA

Ended Nov 28, 2022

Unsupported

PHP 5.6

NA

NA

Ended Dec 31, 2018

Unsupported

Sources: php.net Supported Versions, php.net Unsupported Branches.

The most important takeaways:

  • PHP 8.2 end of life means any team that upgraded to 8.2 after its December 2022 release now has a second migration due by December 31, 2026.
  • PHP 8.4 leaves active support on the same day, so after January 2027 it gets security fixes only, which is fine for production but worth knowing when you pick a target.
  • Is PHP 7.4 end of life? Yes, since November 28, 2022, so when PHP2Shell hit, 7.4 got no official fix, only source-level backports.

Is Patching Enough After PHP2Shell, or Do You Need to Upgrade?

Patching is enough only if you run PHP 8.3, 8.4 or 8.5, which received official fixed releases. PHP 8.2 can patch now but must migrate before December 31, 2026. On PHP 7.4-8.1 you get only source-level backports or a paid LTS vendor’s build, and PHP 5.x-7.3 got no fixes at all, so migration is the only lasting answer.

On October 21, 2026, the PHP project released PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35. They close two remote code execution flaws: CVE-2026-109618 (memory corruption on out-of-memory) and CVE-2026-109619 (stale global state) (NCSC-NL). Each carries a CVSS v4.0 score of 9.2. The vulnerability is in the PHP core, and it doesn’t depend on your application software. The attack needs no authentication and fires before your application code runs, so login screens and app-level checks don’t block it (Datadog Security Labs).

Any installation that serves web requests is exposed: PHP-FPM, Apache mod_php (the highest risk), php-cgi, FrankenPHP and LiteSpeed. Amazon’s MadPot honeypots recorded the first exploitation attempt on October 17, four days before disclosure. (Help Net Security)

Your version

What’s available after PHP2Shell

What to do

8.3-8.5

Official release

Patch and restart PHP workers

8.2

Official release

Patch now, migrate before Dec 31, 2026

7.4-8.1

Source-level backports or LTS vendor patches only

Temporary protection plus a migration plan

5.x-7.3

No fixes from the PHP project

Migrate or modernize; meanwhile a paid LTS vendor or taking the system offline

PHP2Shell is not a one-off: the PHP project ships fixes for PHP vulnerabilities only in supported branches, so any version past the end of life keeps collecting holes that will never be patched officially. 

Workarounds such as disabling session.upload_progress reduce exposure but don’t close the holes. Datadog puts it bluntly: “Only patching fixes these vulnerabilities.” In the US, CISA published an alert Critical Vulnerabilities in PHP Require Immediate Patching, and FBI and NSA guidance for systems that can’t be updated right away is to “take the system offline until it can be fully mitigated”.

What does it mean for a business running on PHP? A single unsupported runtime can turn a security bulletin into severe downtime, and upgrading PHP is how you pay down the technical debt that made the bulletin dangerous.

What Changed in PHP 8.x (8.0 to 8.5)

The difference between PHP 7 and PHP 8 is mostly about stricter typing, errors that throw instead of warning, and a faster engine. PHP 8.0 added JIT, named arguments, union types and match. Each later release added language features and deprecated old behavior, so the jump from 7.4 to 8.4 covers five sets of changes.

PHP 8.0 (2020) brought the Zend Engine 4 and JIT compilation. JIT helps with CPU-bound code such as image processing or calculations, but typical I/O-bound web apps gain little from it, so measure before you count on it. Attributes replaced PHPDoc annotations for structured metadata, which is why Symfony and Doctrine moved routing and mapping to #[Route] and #[ORM\Column].

Version

What’s new

What it deprecates or breaks

8.0

JIT, attributes, named arguments, union types, match, nullsafe operator ?->

String-to-number comparison rules, many warnings become TypeError/ValueError

8.1

Enums, readonly properties, fibers, never type, first-class callable syntax

Passing null to non-nullable internal function parameters

8.2

Readonly classes, DNF types, true/false/null as standalone types

Dynamic properties, ${var} string interpolation, utf8_encode()/utf8_decode()

8.3

Typed class constants, #[\Override], json_validate()

Calling get_class()/get_parent_class() without arguments

8.4

Property hooks, asymmetric visibility, lazy objects, new without extra parentheses

Implicitly nullable parameter types (Foo $x = null); IMAP, OCI8 and Pspell moved to PECL

8.5

Pipe operator |>, #[\NoDiscard], built-in URI extension, array_first()/array_last()

Backtick shell operator, non-canonical casts such as (integer) and (boolean)

PHP 8 Breaking Changes to Check Before You Upgrade

Now let’s talk about PHP 8 breaking changes, which php.net calls backward incompatible changes, and which mostly show up as fatal errors on old syntax, TypeError exceptions where PHP 7 only warned, and deprecation floods from 8.1 onward. 

Automated refactoring tools like Rector, which rewrites old PHP syntax for a newer version, fix most syntax-level issues; behavior changes, such as how strings compare to numbers, need tests and human review. 

The official issue list is long: start with Migrating from PHP 7.4 to 8.0 and PHP 8.4 backward incompatible changes. These are the ones we see break real projects most often:

Change

Version

Symptom

How to fix

Rector?

0 == “abc” is now false

8.0

Silent logic changes in validation and lookups

Find loose comparisons, add tests, switch to ===

No

Curly-brace string offsets $str{0} removed

8.0

Parse error

$str[0]

Yes

each(), create_function() removed

8.0

Fatal error on old libraries

Rewrite with foreach and closures

Partly

Arithmetic on arrays/objects, invalid internal arguments throw

8.0

TypeError instead of a warning

Fix types at the source; add strict types gradually

No

@ no longer hides fatal errors; E_ALL by default

8.0

Errors appear that were always there

Fix them; don’t restore the old level

No

Resources became objects (curl_init() returns CurlHandle)

8.0

is_resource() checks fail

Check !== false or instanceof

Yes

null to non-nullable internal parameters

8.1

Deprecation log floods (strlen(null))

Cast or guard values

Partly

Missing return types on ArrayAccess, Iterator, etc.

8.1

Deprecations in older libraries

Add return types or #[\ReturnTypeWillChange]

Yes

Dynamic properties deprecated

8.2

Deprecations on legacy models and DTOs

Declare properties or use #[\AllowDynamicProperties]

Partly

Implicitly nullable parameters deprecated

8.4

Deprecations across code written for 7.x

?Foo $x = null

Yes

You should treat these deprecations as future fatal errors: a codebase that logs thousands of 8.1 deprecations will hit a wall at PHP 9.

PHP Migration Paths: 7.4 to 8.4 and 8.1/8.2 to 8.4/8.5

Should you upgrade PHP 7.4 directly to 8.4 or go version by version? Both. You step through 8.0, 8.1, 8.2 and 8.3 locally, fixing and committing at each stop, then deploy straight to 8.4 in production. In this way you get small, reviewable diffs without running four production releases.

Why? Because the local steps keep each change set readable. A single 7.4 to 8.4 diff from Rector can touch thousands of files, and when a test fails you won’t know which version’s change caused it. Only the end state goes to production.

From 8.2, the path will be shorter: patch for PHP2Shell first, then move to 8.4. Choose 8.5 only if your full dependency tree already supports it; composer why-not php 8.5 tells you in seconds.

The framework your system runs on usually decides the migration order. You often have to upgrade the framework and PHP in alternating steps, because each version supports a limited PHP range. 

Framework version

Supported PHP

Notes

Laravel 10

8.1-8.3

Out of security support

Laravel 11

8.2-8.4

Security support ended March 2026

Laravel 12

8.2-8.5

Security fixes only until Feb 24, 2027; use as a stepping stone, not a final target

Laravel 13

8.3+

Recommended target for 7.4-era Laravel apps; security fixes until Q1 2028

Symfony 5.4 LTS

7.2.5+ officially; up to 8.3 in practice

No longer developed; Symfony maintainers advise PHP 8.3 at most, though 8.4 may work. We ran 5.4.23 on PHP 8.2.4 with Doctrine, StofDoctrineExtensionsBundle, RedisBundle and EasyAdmin. Upgrade to 6.4 or 7.4 LTS before moving to PHP 8.4

Symfony 6.4 LTS

8.1+

Common stepping stone from 5.4

Symfony 7.4 LTS

8.2+

NA

Symfony 8.0

8.4+

NA

How Long Does a PHP 8 Migration Take?

From our experience, a small, well-tested app usually moves from PHP 7.4 to 8.4 in a few weeks. A mid-size product with framework upgrades takes a few months, and a large legacy system with low test coverage can take six months or even more. The rule here: test coverage and dependency age drive the estimate more than lines of code.

Codebase profile

Typical signs

Duration

Small, maintained

Current framework, 60%+ test coverage, few custom extensions

3-6 weeks

Mid-size product

One or two framework majors behind, partial tests, some abandoned packages

2-4 months

Large legacy

Custom framework or 5.x-era code, little or no tests, business logic in templates

6-12+ months

Top 6 PHP Migration Tools 2026

You can use five tools:

  1. Rector to rewrite code automatically
  2. composer why-not to find blocking packages
  3. PHPStan to catch type errors
  4. PHP_CodeSniffer to keep style consistent 
  5. PHPCompatibility to find incompatible code
  6. AI coding assistants

Together they automate most of a PHP 7 to 8 migration but people still own tests and behavior control.

Tool

What it does

When to run it

Rewrites code with version-specific rule sets; also upgrades Symfony, Laravel, Doctrine and PHPUnit code

At each local version stop

composer why-not php 8.4

Lists packages whose constraints block the target version

Before you start and after each framework upgrade

Static analysis that finds type errors and dead code without running it

After every Rector pass and in CI

Checks and auto-fixes coding standards (PSR-12 or PER Coding Style)

After Rector, so diffs stay readable

Scans code for syntax and functions that break on a target PHP version

Before you start, to size the work

Rector is the closest thing to a PHP 7 to 8 migration tool that does the job end to end. PHPStan is especially useful on legacy code with few tests, because it finds the type problems PHP 8 will now throw at runtime. If you still run PHP 5.6, the same tools apply; Rector has sets from PHP 5.3 upward, so the PHP 5.6 to 7 migration tool and the PHP 7 to 8 one are the same tool.

To size the job before you change anything:

composer why-not php 8.4
composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
composer require --dev phpcompatibility/php-compatibility:"^10.0.0@dev"
vendor/bin/phpcs -ps src --standard=PHPCompatibility --runtime-set testVersion 8.4

PHPCompatibility checks for PHP 8.x live in the 10.x line, which is still in alpha, so install it explicitly as shown above and use as an additional tool; the stable 9.3.5 release won’t flag PHP 8 issues.

8 Steps to Migrate to PHP 8.4

Here’s the PHP 8 upgrade flow our team uses: containerize, upgrade the framework and libraries, switch the PHP version, update Composer, let Rector fix the code, align code style, verify with PHPStan and tests, then test manually.

The examples use a Symfony setup and target PHP 8.4.27, the first 8.4 release with the PHP2Shell fixes.

Step 1. Containerize the application

Docker lets you switch PHP versions on one machine and gives every developer and CI the same runtime. If the app isn’t containerized yet, do that first, while it still runs on 7.4, so the container is the only variable that changes later.

Step 2. Check framework and library support

Check your framework’s documentation for PHP 8 support and upgrade the framework if needed (see the compatibility table above). Then check every library. composer why-not php 8.4 and composer outdated –direct show what’s blocking you. If you’re below 7.4, get to 7.4 first: it’s the last 7.x release and the shortest jump to 8.x.

Step 3. Switch the PHP version

Change the image in docker-compose.yml to the first stop on the route, PHP 8.0: 

services:
  app:
    # was: image: php:7.4.33-fpm
    image: php:8.0-fpm
    volumes:
      - ./:/var/www/html

docker compose up -d --build 

Expect errors at this point: the runtime is on 8.0, but the code and dependencies aren’t.

Steps 3-7 form one iteration. Once tests pass on 8.0, commit and repeat them for 8.1, 8.2, 8.3 and finally PHP 8.4 (php:8.4.27-fpm, the release with the PHP2Shell fixes). Each iteration gives you a small diff you can review and roll back. 

Step 4. Update dependencies with Composer

Set the PHP requirement to the version you just switched to, and pin the platform version so Composer resolves packages for that runtime, not for the PHP on your laptop:

{
  "require": {
    "php": ">=8.0"
  },
  "config": {
    "platform": {
      "php": "8.0.30"
    }
  }
}
{
  "require": {
    "php": ">=8.0"
  },
  "config": {
    "platform": {
      "php": "8.0.30"
    }
  }
}
docker compose exec app composer update --with-all-dependencies

In each iteration, raise both values to the PHP version you’re on now. On the final iteration they become >=8.4 and 8.4.27. If the old PHP version blocked framework upgrades in Step 1, run them now.

Step 5. Fix code with Rector

Install Rector and create rector.php in the project root:

docker compose exec app composer require --dev rector/rector
<?php

use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withPaths([
        __DIR__ . '/src',
        __DIR__ . '/tests',
    ])
    // Applies every PHP-level rule up to 8.4 (replaces SetList::PHP_82 from the 2023 version of this guide)
    ->withPhpSets(php84: true)
    ->withAttributesSets(symfony: true, doctrine: true);

withPaths() lists the folders Rector scans. For the version-by-version route, start with php80: true, commit, and move up one version at a time. Preview first, then apply:

docker compose exec app vendor/bin/rector process --dry-run
docker compose exec app vendor/bin/rector process

Clear the framework cache afterward (bin/console cache:clear in Symfony) or restart the containers.

Step 6. Align code style (optional)

Rector’s output follows its own formatting. Run PHP_CodeSniffer so the diff your reviewers see is about logic, not whitespace:

docker compose exec app composer require --dev squizlabs/php_codesniffer
docker compose exec app vendor/bin/phpcs --standard=PSR12 src
docker compose exec app vendor/bin/phpcbf --standard=PSR12 src

Step 7. Verify with PHPStan and automated tests

Rector uses PHPStan internally to understand types, but that doesn’t replace running PHPStan yourself:

docker compose exec app composer require --dev phpstan/phpstan
docker compose exec app vendor/bin/phpstan analyse src --level=5
docker compose exec app vendor/bin/phpunit

Aim for “No errors” at a level you can hold in CI, then raise it gradually. Run unit and API tests and confirm everything passes.

Step 8. Test manually

Automated tests miss things: a report that renders slightly differently, a payment callback, a cron job that only runs on the first of the month. Run the critical user journeys by hand, ideally with QA engineers who know the product.

Zero-downtime rollout

For production, run the new PHP version side by side with the old one: a separate PHP-FPM pool or container set behind the load balancer, with traffic shifted gradually (blue-green or rolling). Move queue workers and cron jobs separately, because they often run code paths the web tests never touch. Keep the old pool warm until you’ve seen a full business cycle, and write the rollback steps down before you start.

How AI Speeds Up PHP Migration and Where It Doesn’t

AI helps most where Rector stops: code Rector has no rule for, characterization tests for untested legacy code, and explaining what old modules actually do. AI doesn’t replace Rector, static analysis or human review. We use AI inside a pipeline where every change passes independent checks before a person approves it.

Stage

Tool

What AI adds

Find what breaks

PHPCompatibility, composer why-not

Summarizes findings by module and estimates effort

Fix known patterns

Rector (withPhpSets, level sets)

Nothing: deterministic rules beat generated code here

Catch type errors

PHPStan or Psalm

Drafts fixes for errors Rector can’t resolve

Cover legacy behavior

PHPUnit

Generates characterization tests that pin down today’s behavior before the upgrade

Fix the long tail

AI coding agent in a sandbox branch

Rewrites custom extensions, dynamic-property models, abandoned-package replacements

Verify and ship

CI, code review, staged rollout

Nothing; engineers decide

The rules come from our AI-assisted software modernization approach:

  1. Capture existing behavior in tests before AI agents touch anything 
  2. Give the agent narrow authority: it can read and propose, and execute only in a sandbox
  3. Never let the same agent write the fix and provide the only proof it works 
  4. Rector, PHPStan and the test suite are the independent checks

PHP in 2026: Upgrade, Extended Support, or Modernize?

For most engineering teams the answer is to upgrade to PHP 8.4, because extended support from an LTS vendor can buy time but it only postpones the decision. Modernization makes sense when the version is only the visible problem and the architecture is what blocks the business.

Read our PHP application modernization case studies to see how this played out on real products.

Option

When it fits

Cost

Risk

Timeline

Upgrade to PHP 8.4

Supported framework, reasonable tests, architecture that still serves the roadmap

One-off project

Low with tests and staged rollout

Weeks to months

Extended support (LTS vendor)

Upgrade can’t finish before a hard deadline; low-change or soon-to-retire systems

Recurring subscription

You depend on a vendor’s backports; technical debt keeps growing

Immediate

Modernize

Upgrade is blocked by custom frameworks, no tests, or architecture the roadmap can’t build on

Larger, staged investment

Managed through bounded stages

Months, delivered in increments

Then score your system against these criteria to decide:Then score your system against these criteria to decide:

Criterion

Points to upgrade

Points to modernize

Compliance

Auditors only need a supported runtime

Audit findings go beyond the PHP version

Architecture

Framework-based, modular

Custom framework, logic in templates, shared database everyone writes to

Test coverage

Critical journeys covered

Few or no tests

Roadmap

Next features fit the current design

Next features (AI, integrations, scale) need a different design

Budget

One project

Phased budget over several quarters

There’s a middle option we often recommend: a two-phase approach: runtime upgrade + strangler fig pattern

Phase one upgrades the runtime to 8.4 with minimal code changes, so security stops being urgent. Phase two moves the worst modules behind new services one at a time, with old and new running side by side until the new path earns trust.

How MobiDev Can Help

MobiDev has been building and modernizing custom software since 2009, and our PHP team has migrated production apps across framework and PHP versions, including Symfony. We start with a software audit: dependencies, test coverage, PHP2Shell exposure, and a timeline you can plan around. Then we run the migration in your repository, with your in-house team, and roll out without downtime.

If the upgrade turns out to be the start of a bigger job, our legacy PHP modernization services cover the next stage.

Need a Tech Consultation on PHP Migration? Schedule a call

PHP 8 Migration FAQ

Can't Find Your Answer? Book a call or send us an email

MobiDev is a Trusted
Development Company

9.1 NPS

As of the 2025 Client Survey

400+

Total Number of Clients

5+ years

The Average Client Retention

4.9/5

Review Rating on Clutch

NUCC Chambre of Commerce
Forbes Technology Council
Top AI Comapnies Clutch
Upwork Best Agency
Top Sofware Development Company
AWS certified Solution Architect Associate
Newsweek

Let's Talk About Your Product