PHP 7: Maintaining and Upgrading Legacy Apps · Working in a Legacy PHP Codebase

Taming Globals, Includes and Superglobals

Contain global state so code becomes testable and predictable.

Legacy PHP often uses global $db;, static singletons, and direct $_GET/$_POST/$_SESSION access deep in functions. You can't remove these overnight, but you can contain them: create objects at the bootstrap and pass them down, wrap superglobals in a request object, and replace global with constructor or function parameters as you touch code.

<?php
// Before
function getUserOrders() {
    global $db;
    $id = $_SESSION['user_id'];
    return mysqli_query($db, "SELECT * FROM orders WHERE user_id = $id");
}

// After (introduced gradually)
function getUserOrders(PDO $db, int $userId): array
{
    $stmt = $db->prepare('SELECT * FROM orders WHERE user_id = ?');
    $stmt->execute([$userId]);
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

// Call site keeps working in old code:
$orders = getUserOrders($GLOBALS['pdo'], (int) $_SESSION['user_id']);

Common pitfalls

  • Global state that makes tests depend on order and environment.
  • Reading superglobals deep in business logic.
  • Static singletons that can't be replaced in tests.

Key points

  • Pass dependencies explicitly
  • Read superglobals only at the edge
  • Refactor call sites incrementally

Further reading: PHP manual: variable scope

Practice questions

Why are global variables a problem for testing?

Answer

Functions depend on hidden state that tests must set up globally, tests can interfere with each other, and you can't substitute fakes easily.

How can you refactor away from global $db without a big bang?

Answer

Add a parameter for the dependency, update call sites gradually (passing the existing global at first), and eventually create the dependency in one bootstrap/container.

Open in the app to mark lessons complete, take notes and track your progress.