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\n1. 添加特征测试
\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\n2. 创建测试“覆盖层”
\n\n从集成测试开始,检查关键用户场景(注册、下单等)。这将在重构时提供基本信心。
\n\nPHP 遗留代码重构的分步策略
\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// 在模板