PHP 怎么PHP 遗留重构

wen PHP项目 2

本文目录导读:

PHP 怎么PHP 遗留重构

  1. 重构前的评估与准备
  2. 分层重构策略(推荐顺序)
  3. 推荐的重构模式
  4. 实战工具
  5. 一个典型的重构流程示例
  6. 常见陷阱与应对
  7. 当遇到极端遗留代码怎么办?
  8. 重构路线图

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_dumpmonolog/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 中:

  1. 加测试:用 PHPUnit 写 3 个用例(正常用户、无订单用户、SQL 注入尝试)
  2. 抽离:创建 OrderService::getUserOrders()
  3. 替换数据库层mysqli_query → Doctrine DBAL
  4. 引入 Value ObjectUserId 替代 int $id
  5. 注入依赖:将 DatabaseConnection 通过构造函数传入
  6. 灰度:通过 Feature Flag 让 10% 用户走新路径
  7. 验证:对比新旧方法返回数据是否一致
  8. 移除旧代码:删除 index.php 中老函数

常见陷阱与应对

陷阱 对策
试图一次重构所有 拆分成小步,每次只改一个逻辑
没有测试就改 没有测试的重构不是重构,是重写
修改数据库 schema 新建字段/表,旧字段暂保留,用迁移脚本
忽略性能回归 每次改动执行 blackfire.io 或 Xdebug 性能剖析
团队抗拒变化 引入 Code Review,展示重构带来的可维护性提升

当遇到极端遗留代码怎么办?

如果代码是 无测试、无文档、无类型、无注释 的“三无”代码:

  1. 先不要改业务逻辑,只添加类型声明和测试
  2. 使用 Parameter Object 减少函数参数
  3. extract() 解构复杂数组的遗留代码?重构它
  4. 对于不可测试的全局依赖,使用 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% 的时间做技术债务清理,逐步将项目引入正轨。

抱歉,评论功能暂时关闭!