Stepwise upgrade off an unsupported Laravel and PHP release

The stack no longer received security fixes, and a previous upgrade attempt had been abandoned halfway, leaving a stale branch nobody trusted to merge.

Engagement

Role: Upgrade service, founder-led.

Shape: Compatibility report, then one reviewable pull request per version hop.

Duration: 2–3 weeks depending on package debt.

Stack: Laravel 8 → 11 · PHP 7.4 → 8.3 · MySQL · PHPUnit · Rector · GitHub Actions.

What was wrong

  • The framework and PHP branch no longer received security fixes.
  • A previous upgrade attempt existed as a stale long-lived branch that nobody trusted to merge.
  • Two Composer packages were abandoned upstream with no upgrade path to the target framework release.
  • The test suite could not run on the target PHP version, so nothing verified the migration.

What was built

  1. Compatibility report mapping each breaking change between the current and target release to the exact files affected.
  2. Abandoned packages replaced with maintained equivalents, in isolated pull requests so each swap could be reviewed on its own.
  3. One version hop per pull request with a green test run, instead of a single unreviewable jump.
  4. Documented smoke pass over auth, billing, queues, scheduled jobs and outbound mail before the cutover window.

Architecture after the work

Compatibility report first — Every breaking change between the current and target release mapped to the exact files affected, so the scope was known before code changed.

Package swaps in isolation — Abandoned packages replaced with maintained equivalents in their own pull requests, each reviewable on its own merits.

One hop per pull request — Each framework version step lands separately with a green test run, instead of a single unreviewable jump.

Rollback written before cutover — The rollback plan, including database migration reversal, was agreed in writing before the cutover window opened.

Trade-offs and what was left out

  • Deprecated helpers were replaced only where the target release removed them; cosmetic modernisation was deliberately excluded.
  • The stale upgrade branch was abandoned rather than merged — reconstructing its intent would have cost more than redoing the work in reviewable steps.

Outcome

The application runs on a supported Laravel and PHP branch, with a rollback plan that was written before the cutover rather than improvised during it.

Frequently asked questions

How long does a Laravel upgrade take?
Two to three weeks is typical for one or two major version hops, and the variable is package debt rather than the framework itself. Abandoned or heavily patched packages are what extends an upgrade, which is why the compatibility report is produced before any price is agreed.
Will the upgrade break the application?
The process is designed so that a break is caught in a pull request, not in production: the test suite runs on every hop, the risky paths get smoke-tested manually before cutover, and a rollback plan exists in writing beforehand.

Related services