本文目录导读:

PHP遗留系统重构是一个系统工程,需要平衡业务稳定与技术改进,下面从评估、策略、步骤、模式、工具几个核心维度,给你一个可落地的重构路线图。
重构前的评估与准备
目标明确
- 为什么重构?(性能?可维护性?迁移新框架?)
- 重构范围(全量 / 模块级别 / 函数级别)
- 风险承受度(业务中断容忍度、回滚方案)
现状摸底
- 代码仓库:Git 仓库?有无历史版本?
- 测试覆盖:单元测试 / 集成测试覆盖率(< 10%)
- 依赖分析:composer.json / 自建 autoload?第三方库版本过老?
- 技术债务:
error_reporting设置?global变量滥用?mysql_*函数?
建立安全网
- 单元测试:用 PHPUnit 为核心逻辑(尤其数据库、API)添加测试,确保回归
- 集成测试:对关键业务流程(订单、支付)用
Laravel Dusk/Codeception做 E2E - 数据库备份:重构前全量备份 + 迁移脚本
- 灰度发布:通过 Feature Flag(如
laravel-pennant)按用户比例发布新代码
分层重构策略(推荐顺序)
第一层:基础设施重构(零风险快速收益)
PHP 版本升级
- 从 5.x → 7.4 → 8.x(每步验证兼容性)
- 强制启用
strict_types - 替换废弃函数(
mysql_*→mysqli_*/ PDO,each()→foreach)
引入 Composer 依赖管理
- 将手写 autoload 改为 composer.json
autoload.psr-4 - 引入
symfony/var-dumper替代var_dump、monolog/monolog替代error_log
错误处理规范化
// 旧
if (!$result) { echo "数据库错误"; }
// 新
try {
$result = $db->query($sql);
} catch (\PDOException $e) {
(new \Monolog\Logger('db'))->error($e->getMessage(), ['sql' => $sql]);
throw new \RuntimeException('数据库操作失败');
}
第二层:代码结构重构(面向对象化)
模式 1:过程化代码 → 类
// 旧(遗留)
function get_user($id) {
global $db;
$res = $db->query("SELECT * FROM users WHERE id=$id");
return $res->fetch_assoc();
}
// 新(面向对象)
interface UserRepository {
public function findById(int $id): ?User;
}
class MySqlUserRepository implements UserRepository {
public function __construct(private \PDO $pdo) {}
public function findById(int $id): ?User {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);
$data = $stmt->fetch(\PDO::FETCH_ASSOC);
return $data ? new User(...$data) : null;
}
}
模式 2:Spaghetti Controller → Action 类
- 将
index.php?action=users&do=list中的逻辑抽离到独立类 - 每个 Action 类实现
__invoke(Request $request): Response
模式 3:Global 变量 → 依赖注入
- 使用 PHP-DI / Laravel Container 替代
global $db, $config, $logger - 构造函数注入或属性注入(PHP 8+ 支持
#[Autowired])
// 遗留
function processPayment($orderId) {
global $db, $mail, $paymentGateway;
// ...
}
// 重构后
class PaymentProcessor {
public function __construct(
private OrderRepository $orders,
private Mailer $mailer,
private PaymentGateway $gateway
) {}
public function process(OrderId $orderId): PaymentResult { ... }
}
第三层:业务逻辑重构(最复杂)
策略 1:提取业务服务层
- 将
UserService::register()从控制器中剥离 - 业务逻辑不依赖 HTTP / 数据库细节
策略 2:替换遗留数据库抽象层
- 从
mysqli_query替换为 Doctrine DBAL / Eloquent - 逐步迁移,不需要一次重写所有查询
策略 3:引入值对象
// 旧
function createUser($name, $email) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { ... }
}
// 新
readonly class Email {
public function __construct(public string $value) {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException("Invalid email: $value");
}
}
}
推荐的重构模式
| 模式 | 适用场景 | PHP 实现 |
|---|---|---|
| Strangler Fig Pattern | 逐步替换核心模块 | 新接口 Route 旧代码,旧代码逐步下线 |
| Branch by Abstraction | 替换底层实现(如缓存、队列) | 先定义接口,新旧实现并存,切换 |
| Parallel Run | 高风险数据迁移 | 新旧代码同时写数据,对比差异 |
| Feature Toggle | 按用户灰度发布新逻辑 | config('feature.new-checkout') 开/关 |
| Replace Function with Function | 纯函数替换 | 先加 deprecation 通知,再移除 |
实战工具
| 工具 | 用途 |
|---|---|
| Rector | 自动代码升级(PHP 5→8,框架升级) |
| PhpStan / Psalm | 静态分析,发现潜在错误 |
| PHP CS Fixer | 代码风格统一 |
| PHP Insights | 代码质量评分与建议 |
| Deptrac | 依赖层次检查(防止循环依赖) |
| Laravel Shift | Laravel 项目自动升级服务 |
| PHPUnit / Pest | 测试覆盖,确保重构不破坏业务 |
一个典型的重构流程示例
假设有个旧函数 getUserOrders() 写在一个 500 行的 index.php 中:
- 加测试:用 PHPUnit 写 3 个用例(正常用户、无订单用户、SQL 注入尝试)
- 抽离:创建
OrderService::getUserOrders() - 替换数据库层:
mysqli_query→ Doctrine DBAL - 引入 Value Object:
UserId替代int $id - 注入依赖:将
DatabaseConnection通过构造函数传入 - 灰度:通过 Feature Flag 让 10% 用户走新路径
- 验证:对比新旧方法返回数据是否一致
- 移除旧代码:删除
index.php中老函数
常见陷阱与应对
| 陷阱 | 对策 |
|---|---|
| 试图一次重构所有 | 拆分成小步,每次只改一个逻辑 |
| 没有测试就改 | 没有测试的重构不是重构,是重写 |
| 修改数据库 schema | 新建字段/表,旧字段暂保留,用迁移脚本 |
| 忽略性能回归 | 每次改动执行 blackfire.io 或 Xdebug 性能剖析 |
| 团队抗拒变化 | 引入 Code Review,展示重构带来的可维护性提升 |
当遇到极端遗留代码怎么办?
如果代码是 无测试、无文档、无类型、无注释 的“三无”代码:
- 先不要改业务逻辑,只添加类型声明和测试
- 使用 Parameter Object 减少函数参数
- 用
extract()解构复杂数组的遗留代码?重构它 - 对于不可测试的全局依赖,使用 Wrapper
// 代替直接调用 time() interface Clock { public function now(): DateTimeImmutable; } class SystemClock implements Clock { public function now(): DateTimeImmutable { return new DateTimeImmutable(); } }
重构路线图
加测试(安全网)→ 2. 升级 PHP/工具链 → 3. 提取实体/值对象
→ 4. 引入依赖注入 → 5. 拆分为微服务/模块 → 6. 移除老旧代码
↓ ↓
每个步骤: Feature Toggle → 灰度 → 验证 → 移除旧代码
最后提醒:遗留重构不是一次性的活动,而需要持续投入,建议每周分配 20% 的时间做技术债务清理,逐步将项目引入正轨。