Skip to main content
RefoundRefound
All articles
phplegacy-migrationstrangler-figlaravel

Zero-downtime PHP 5.6 to 8.3 migration: how we did it in 14 weeks

Niclas Kusenbach

When a regional freight company approached me, their core order management system was running on PHP 5.6. The codebase was 11,000 lines of procedural, deeply coupled PHP, talking to a MySQL 5.5 database. There was no framework, no dependency injection, and crucially, zero test coverage.

The system was business-critical. If it went down, trucks didn't leave the depot.

This is the story of how we migrated it to PHP 8.3 and Laravel over 14 weeks, with zero minutes of downtime.

The Problem with Big Bang Rewrites

The client's initial thought was to simply build a "V2" from scratch and switch over on a weekend. This is a classic trap. Big bang rewrites of legacy systems almost always fail because:

  1. The legacy codebase contains years of undocumented business logic and edge cases.
  2. By the time V2 is finished, the business requirements have changed.
  3. The switch-over weekend is a high-stress, high-risk event that usually ends in rollbacks.

The Solution: The Strangler Fig Pattern

Instead of a rewrite, we used the Strangler Fig Pattern. We set up a modern Laravel application running on PHP 8.3 alongside the legacy application.

We configured the web server (Nginx) to route traffic:

  • If a route existed in the new Laravel app, Nginx served it from Laravel.
  • If the route didn't exist in Laravel, Nginx fell back to the old PHP 5.6 application.

This allowed us to migrate the application one route, one feature at a time.

Step 1: Database Abstraction

The hardest part of a strangler migration is the database. Both applications needed to share the same MySQL database during the transition.

We created Eloquent models in Laravel that mapped to the legacy table structures. We didn't change the schema; we just changed how we talked to it. This allowed the new code to read and write data that the old code could still understand.

Step 2: Incremental Feature Migration

We started with the lowest-risk features—read-only views like the order history dashboard. We rebuilt the view in Laravel, pushed it to production, and Nginx automatically started routing traffic to the new code.

If there was a bug, we could instantly roll back the Nginx rule, directing traffic back to the old, working code. The risk was almost zero.

Step 3: Tackling the Core

Once we had momentum, we tackled the complex, write-heavy core: the order creation flow. This is where the lack of tests in the legacy system was dangerous.

We wrote comprehensive PHPUnit tests against the new Laravel code to define the expected behavior. We ran both systems in parallel for a short time, comparing the outputs (shadow writing) before officially switching the route.

The Result

After 14 weeks, the last route was migrated to Laravel. We deleted the legacy PHP 5.6 folder.

The result:

  • Downtime: 0 minutes.
  • Test Coverage: Went from 0% to 68% on critical paths.
  • Deployment: Went from a stressful 2-hour manual process to an automated 5-minute GitHub Actions pipeline.

Legacy modernization doesn't have to be a nightmare. With the right strategy, it's a predictable, incremental engineering process.