Refactoring Legacy PHP Code: From A to Z — Complete Guide

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Refactoring Legacy PHP Code: From A to Z



Working with legacy code is an inevitable part of any PHP developer's life. Sooner or later, you will encounter a project written in PHP 5.6 (or even 4), where there is no autoloading, global variables are used, and business logic is mixed with HTML. Refactoring such code is not just "tidying up," but a strategic process that can save a project from technical debt and make it maintainable.



In this article, we will break down how to approach refactoring legacy PHP code systematically, without breaking working functionality, and turn a "swamp" into modern, testable code.



1. Diagnosis and Assessment: Where to Start?



Before changing code, you need to understand what you're dealing with. Start with a project audit.



Step 1. Identify "Pain Points"

  • Dependencies: Are outdated libraries used? For example, mysql_* functions instead of PDO or MySQLi.
  • Architecture: Is there any structure (MVC)? Or is all the code in one index.php file?
  • Tests: Do automated tests exist? If not, this is your first priority.


Step 2. Analysis Tools

Use static analyzers to quickly identify problem areas:

  • PHPStan / Psalm: Will find type errors and signature mismatches.
  • Rector: Automatically updates syntax for a new PHP version.
  • PhpMetrics: Will show cyclomatic complexity and code cohesion.


Example of running Rector to upgrade from PHP 7.4 to 8.0:

vendor/bin/rector process src/ --set php80


2. The "Painless Surgery" Strategy: A Step-by-Step Plan



Refactoring legacy code is not a "big bang." It is a series of small, safe steps. Use the "Strangler Fig" technique — gradually replace old modules with new ones, without rewriting everything at once.



Step 1. Cover the Code with Tests

Without tests, refactoring is playing Russian roulette. Start with characterization tests:

class LegacyCalculatorTest extends TestCase {    public function testOldCalculation(): void {        // Run the old code and record the result        $result = legacy_calculation(10, 5);        $this->assertEquals(15, $result);    }}

This creates a "safety net" before making changes.



Step 2. Isolate "Dirty" Logic

If the code works with global variables ($GLOBALS), move them into function parameters:

// Was (global dependency)function processOrder() {    global $db;    $result = $db->query("SELECT * FROM orders");}

// Became (dependency injection)function processOrder(PDO $db) { $result = $db->query("SELECT * FROM orders");}


Step 3. Replace Deprecated Functions

For example, replacing mysql_* with PDO:

// Old code$link = mysql_connect('localhost', 'user', 'pass');mysql_select_db('test', $link);$result = mysql_query("SELECT * FROM users", $link);

// New code (PDO)$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');$stmt = $pdo->query("SELECT * FROM users");$result = $stmt->fetchAll(PDO::FETCH_ASSOC);


3. Typical Legacy PHP Problems and Their Solutions



Problem 1: "Spaghetti Code" (Mixing Logic and Presentation)

Solution: Separate the code into layers. Move business logic into separate classes, and HTML into templates (e.g., using Twig).

// Bad: logic and HTML in one fileif ($user->isAdmin()) {    echo "<h1>Hello, admin!</h1>";} else {    echo "<h1>Hello, user!</h1>";}

// Good: controller + template//

Blogs

Book Recommendations