本文目录导读:

- 第一步:建立“安全网”(最高优先级)
- 第二步:处理“最大债务”——遗留代码(Legacy Code)
- 第三步:升级“地基”——PHP 版本与依赖(Medium)
- 第四步:清理“数据层”债务(High)
- 第五步:重构“业务逻辑”(Long-term)
- 第六步:基础设施与监控(Quick wins)
- 避坑指南(特别提醒)
- 实战案例(小 Demo)
在 PHP 项目中,技术债(Technical Debt)是普遍存在的,还债的核心思路是“步步为营,先把风险最高的地方补上,而不是一次性推倒重来”。
以下是针对 PHP 项目的系统性还债策略,按优先级从高到低排列:
第一步:建立“安全网”(最高优先级)
在改任何代码之前,如果没有测试保护,重构就是在走钢丝。
- 引入 PHPUnit 或 Pest:
- 为核心业务逻辑(如订单计算、支付回调、权限判断)编写单元测试。
- 为关键用户路径(如登录、下单)编写 Feature 测试。
- 开启静态分析:
- PHPStan 或 Psalm:运行
vendor/bin/phpstan analyse -l max,先把报错数量清零,再逐步提升等级(Level)。 - PHP_CodeSniffer:统一代码风格(PSR-12)。
- PHPStan 或 Psalm:运行
- 接入 CI/CD:在 GitHub Actions 或 GitLab CI 中,强制运行测试和静态分析,让“红牌”阻止合并,防止新债产生。
第二步:处理“最大债务”——遗留代码(Legacy Code)
PHP 老项目常见的问题是没有命名空间、函数全局混杂、直接操作 MySQL。
策略:渐进式重构,而非重写
- 引入自动加载(Composer):
- 如果没有用 Composer,先引入,将
require/include逐步替换为 PSR-4 自动加载。
- 如果没有用 Composer,先引入,将
- “绞杀者模式”(Strangler Pattern):
- 不要试图一次性重写整个项目,新建一个
App命名空间(PSR-4)。 - 写新功能时,在新命名空间下用现代 PHP(类型声明、依赖注入)编写。
- 老代码先留着,通过“适配器”调用,随着时间推移,新代码逐渐替代旧代码。
- 不要试图一次性重写整个项目,新建一个
- 解耦 God Object(上帝类):
- 如果某个类有 5000 行代码,先使用 IDE(如 PhpStorm)的“提取接口”和“提取方法”功能,把大方法拆成小方法,把不相关的职责拆成独立的类(如把
User类中的邮件发送逻辑拆到Mailer)。
- 如果某个类有 5000 行代码,先使用 IDE(如 PhpStorm)的“提取接口”和“提取方法”功能,把大方法拆成小方法,把不相关的职责拆成独立的类(如把
第三步:升级“地基”——PHP 版本与依赖(Medium)
升级 PHP 版本是性价比极高的还债方式,性能和安全都会大幅提升。
- 小步升级:如果你还在 PHP 7.4,先升到 8.1,不要直接跳 8.4(因为很多扩展可能不兼容)。
- 使用 Rector 进行自动化升级:
vendor/bin/rector process src --set php81(Rector 能自动修改废弃函数、语法,能帮你把mysql_query改成mysqli或 PDO)。
- 依赖管理:
- 运行
composer outdated,对于不再维护的包,寻找替代品或重写。 - 锁死版本:确保
composer.lock在版本控制中,保证环境一致。
- 运行
第四步:清理“数据层”债务(High)
老 PHP 最痛的是 SQL 注入和混乱的数据库操作。
- 全面使用 PDO 或 Doctrine DBAL:
- 全局搜索
mysql_或mysqli_query,强制替换为 PDO 预处理语句,这是安全红线,优先级最高。
- 全局搜索
- 引入 Migration(迁移):
- 使用 Phinx 或 Doctrine Migrations,将 DDL(建表语句)写成 SQL 文件入库。
- 废除
dump.sql和手动修改数据库的习惯,确保表结构变更可回滚、可追溯。
第五步:重构“业务逻辑”(Long-term)
- 消灭全局变量:
- 把
global $db或$config替换为依赖注入(DI)容器(如 PHP-DI 或 Laravel 的容器)。
- 把
- 类型安全:
- 给函数参数和返回值加上
int,string,array类型声明。 - 如果历史原因不能改,可以使用
assert()或@param注释过渡,但最终目标是强类型。
- 给函数参数和返回值加上
- 处理“魔法数字”:
- 把
if ($status == 1)改成if ($order->isPaid())或使用常量OrderStatus::PAID。
- 把
第六步:基础设施与监控(Quick wins)
- 启用 OPcache:让运行速度直接提升 30%。
- 日志:引入 Monolog,废除
error_log写 txt 的习惯,接入 ELK 或 Sentry。 - 错误处理:废除 抑制符,使用
try-catch捕获异常。
避坑指南(特别提醒)
- 不要重写! 如果业务代码超过 10 万行,重写大概率会导致项目失败。
- 保持“童子军原则”:每次接触某段代码时,顺手把那一小块区域整理干净(重命名一个变量、抽取一个小函数)。
- 时间盒:每周设定固定 3 小时专门做重构,不计入常规需求排期。
实战案例(小 Demo)
假设你有一段老代码:
// 旧代码:没有类型,直连数据库,逻辑混乱
function get_user($id) {
$result = mysql_query("SELECT * FROM users WHERE id=" . $id);
$row = mysql_fetch_array($result);
if ($row['active'] == 1) {
return $row['email'];
} else {
return null;
}
}
还债第一步(安全):
// 改成 PDO 预处理
function get_user(int $id): ?string {
$stmt = $pdo->prepare("SELECT email, active FROM users WHERE id = :id");
$stmt->execute(['id' => $id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return ($row && $row['active'] === 1) ? $row['email'] : null;
}
还债第二步(结构):
// 放到 UserRepository 类中,注入 PDO
final class UserRepository {
public function __construct(private PDO $pdo) {}
public function findActiveEmailById(int $id): ?string {
$stmt = $this->pdo->prepare("SELECT email FROM users WHERE id = :id AND active = 1");
$stmt->execute(['id' => $id]);
$email = $stmt->fetchColumn();
return $email ?: null;
}
}
PHP 还债的核心不是“写新代码”,而是“建立安全网 -> 隔离旧代码 -> 局部更新”,只要静态分析通过率在提升,测试覆盖率在增加,你就是在正确的还债路上。