Lesson 24 / 25
Legacy PHP Versus Modern PHP
Recognise outdated practices and modernise PHP code step by step.
Leaving bad habits behind
Much PHP advice online predates modern PHP. Practices to avoid: the removed mysql_* functions and SQL built by string concatenation; md5 or sha1 for passwords; extract() and variable variables; global state and global keywords; mixing HTML, SQL and business logic in one file; suppressing errors with @; relying on loose == comparisons; untyped functions and arrays of arrays everywhere; and include chains instead of autoloading. Modern equivalents: PDO or an ORM with prepared statements, password_hash, typed classes and value objects, dependency injection, templates with auto-escaping, strict_types, enums, readonly classes, Composer autoloading, PSR interfaces, static analysis and automated tests. Modernise legacy code incrementally: add Composer and autoloading, add tests around critical paths (characterisation tests), raise PHPStan levels with a baseline, introduce types, use Rector to automate syntax upgrades, and replace hand-written plumbing with framework components one area at a time, the strangler approach applied inside a codebase.
Legacy code and its modern rewrite
Same behaviour: fetch a user's orders.
<?php
// legacy (do not do this)
// $id = $_GET['id'];
// $res = mysqli_query($conn, "SELECT * FROM orders WHERE user_id = $id"); // SQL injection
// while ($row = mysqli_fetch_assoc($res)) { echo "<li>" . $row['title'] . "</li>"; } // XSS
// modern
declare(strict_types=1);
final readonly class OrderSummary
{
public function __construct(public string $id, public string $title, public OrderStatus $status) {}
}
final class OrderQueries
{
public function __construct(private readonly PDO $pdo) {}
/** @return list<OrderSummary> */
public function forUser(int $userId): array
{
$stmt = $this->pdo->prepare('SELECT id, title, status FROM orders WHERE user_id = :uid ORDER BY created_at DESC');
$stmt->execute(['uid' => $userId]);
return array_map(
fn (array $r): OrderSummary => new OrderSummary($r['id'], $r['title'], OrderStatus::from($r['status'])),
$stmt->fetchAll(PDO::FETCH_ASSOC),
);
}
}
// template: <li><?= e($order->title) ?></li> (escaped output)Upgrade in small, tested steps
Big-bang rewrites of legacy PHP applications often fail. Add tests around a feature, modernise it, ship it, and repeat. Rector and PHPStan make each step mechanical and safe.
Quick check: Which practice belongs to modern PHP?
- Prepared statements with PDO and strict types
- Using mysql_* functions
- Hashing passwords with md5
- Suppressing errors with @
Answer
Prepared statements with PDO and strict types — Prepared statements and strict types are core modern practices; the others are outdated and unsafe.