Refactoring Legacy PHP Code: From A to Z for Beginners

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

\n\n

Working with legacy PHP code is an inevitable reality for many developers. Projects written 5-10 years ago often represent "spaghetti code" with global variables, mixing business logic and presentation, lack of typing, and outdated functions (mysql_*, ereg, etc.). In this article, we will break down a step-by-step approach to refactoring legacy PHP code that allows you to improve the project without stopping its operation.

\n\n

Why is Refactoring Legacy Code Difficult?

\n\n

The main problem with legacy code is the lack of tests. You cannot be sure that changes won't break existing functionality. Additional challenges:

\n\n
    \n
  • Lack of documentation — the logic of operation is often unclear
  • \n
  • Mixing layers — SQL queries inside HTML templates
  • \n
  • Global state — variables accessible from any point in the code
  • \n
  • Outdated patterns — using register_globals, mysql_* functions
  • \n
  • Hard dependencies — direct object creation using new inside classes
  • \n
\n\n

Preparation for Refactoring: Creating a "Safety Net"

\n\n

Before changing the code, you need to ensure you can catch errors. Here are the mandatory steps:

\n\n

1. Adding Characterization Tests

\n\n

Characterization tests capture the current behavior of the code. They are written after the code is written and allow you to ensure that refactoring hasn't changed the behavior.

\n\n
<?php\n// Example of a characterization test for a legacy function\nclass LegacyCalculatorTest extends PHPUnit\\Framework\\TestCase\n{\n    public function testCalculateTotal()\n    {\n        // Capture current behavior\n        $result = legacy_calculate_total(100, 0.2);\n        $this->assertEquals(120, $result);\n    }\n}\n
\n\n

2. Creating a "Blanket" of Tests

\n\n

Start with integration tests that check key user scenarios (registration, order placement, etc.). This will provide basic confidence during refactoring.

\n\n

Step-by-Step Strategy for Refactoring Legacy PHP Code

\n\n

We use the "Strangler Fig Pattern" approach — gradually replacing old components with new ones without stopping the system.

\n\n

Step 1: Isolate and Encapsulate Global State

\n\n

Global variables and functions are the main enemy of refactoring. Start by encapsulating them:

\n\n
<?php\n// BEFORE: global function and variable\n$db = mysql_connect('localhost', 'user', 'pass');\nfunction get_user($id) {\n    global $db;\n    $result = mysql_query("SELECT * FROM users WHERE id = $id", $db);\n    return mysql_fetch_assoc($result);\n}\n\n// AFTER: class with explicit dependency\nclass UserRepository {\n    private PDO $pdo;\n    \n    public function __construct(PDO $pdo) {\n        $this->pdo = $pdo;\n    }\n    \n    public function findById(int $id): ?array {\n        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = ?');\n        $stmt->execute([$id]);\n        $user = $stmt->fetch();\n        return $user ?: null;\n    }\n}\n
\n\n

Step 2: Separate Logic from Presentation

\n\n

Typical legacy code contains SQL and PHP logic directly in HTML. Use the "Template Method" pattern for separation:

\n\n
<?php\n// BEFORE: spaghetti code in the template\n?>\n<ul>\n<?php\n$result = mysql_query("SELECT * FROM products");\nwhile ($row = mysql_fetch_assoc($result)) {\n    echo "<li>" . $row['name'] . " - " . $row['price'] . " rub.</li>";\n}\n?>\n</ul>\n\n// AFTER: extracting logic into a service\nclass ProductService {\n    public function __construct(private ProductRepository $repo) {}\n    \n    public function getProductList(): array {\n        return $this->repo->findAll();\n    }\n}\n\n// In the template                

Blogs

Book Recommendations