从零开始重构 PHP 遗留代码:初学者完全指南

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

PHP 遗留代码重构:从零开始

\n\n

处理过时的(遗留)PHP 代码是许多开发者不可避免的现实。5-10 年前编写的项目通常是“意大利面条式代码”,包含全局变量、业务逻辑与视图混合、缺乏类型检查以及过时的函数(mysql_*、ereg 等)。在本文中,我们将逐步介绍重构遗留 PHP 代码的方法,使您能够在不中断项目运行的情况下改进它。

\n\n

为什么重构遗留代码很困难?

\n\n

遗留代码的主要问题在于缺乏测试。您无法确定修改是否会破坏现有功能。其他难点包括:

\n\n
    \n
  • 缺乏文档——业务逻辑往往难以理解
  • \n
  • 层次混杂——SQL 查询出现在 HTML 模板中
  • \n
  • 全局状态——变量可从代码的任何位置访问
  • \n
  • 过时的模式——使用 register_globals、mysql_* 函数
  • \n
  • 紧耦合依赖——在类内部直接通过 new 创建对象
  • \n
\n\n

重构准备:建立“安全网”

\n\n

在修改代码之前,必须确保能够捕获错误。以下是必要步骤:

\n\n

1. 添加特征测试

\n\n

特征测试(characterization tests)用于记录代码的当前行为。它们在代码编写之后编写,确保重构不会改变行为。

\n\n
<?php\n// 遗留函数的特征测试示例\nclass LegacyCalculatorTest extends PHPUnit\\Framework\\TestCase\n{\n    public function testCalculateTotal()\n    {\n        // 记录当前行为\n        $result = legacy_calculate_total(100, 0.2);\n        $this->assertEquals(120, $result);\n    }\n}\n
\n\n

2. 创建测试“覆盖层”

\n\n

从集成测试开始,检查关键用户场景(注册、下单等)。这将在重构时提供基本信心。

\n\n

PHP 遗留代码重构的分步策略

\n\n

采用“绞杀藤模式”(Strangler Fig Pattern)——逐步用新组件替换旧组件,无需停止系统运行。

\n\n

步骤 1:隔离并封装全局状态

\n\n

全局变量和函数是重构的主要敌人。从封装它们开始:

\n\n
<?php\n// 之前:全局函数和变量\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// 之后:具有显式依赖的类\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

步骤 2:将逻辑与视图分离

\n\n

典型的遗留代码包含直接嵌入 HTML 的 SQL 和 PHP 逻辑。使用“模板方法”模式进行分离:

\n\n
<?php\n// 之前:模板中的意大利面条式代码\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'] . " 卢布.</li>";\n}\n?>\n</ul>\n\n// 之后:将逻辑提取到服务中\nclass ProductService {\n    public function __construct(private ProductRepository $repo) {}\n    \n    public function getProductList(): array {\n        return $this->repo->findAll();\n    }\n}\n\n// 在模板                

博客

书籍推荐