本文目录导读:

- 第一阶段:资产盘点与风险评估(要先搞清楚“遗产”到底有多重)
- 第二阶段:制定处理策略(从“动不了”到“能动”的路线图)
- 第三阶段:执行与工具链(硬仗怎么打)
- 第四阶段:长期维护与防范(别让新代码变遗产)
- 投入产出比建议(优先级排序)
- 最后提醒:不要试图一次搞定
处理PHP代码遗产(Legacy Code)是一个系统工程,需要综合考虑技术债务、业务连续性和团队能力,处理不好会陷入“不敢改、改不动、天天救火”的泥潭。
直接给出一个结构化的处理框架,分为评估、策略、执行、防范四个阶段。
第一阶段:资产盘点与风险评估(要先搞清楚“遗产”到底有多重)
代码审计与分类
- 年代与版本:区分是 PHP 5.x、7.x 还是早期的 4.x,PHP 5.x 已 EOL,存在严重安全风险,是处理的重点。
- 技术债扫描:使用静态分析工具(如 PHPStan、Phan 或 SonarQube)扫描出:
- 高危漏洞:SQL 注入、XSS、文件包含、反序列化漏洞。
- 已弃用特性:
mysql_*函数、ereg系列、each、flush等。 - 糟糕实践:
eval、extract、global滥用、硬编码密码、无命名空间的类。
- 依赖清单:检查
composer.json(如果有)、include_path、手动引入的第三方库版本。
业务影响性评估
- 关键路径:哪些代码是核心业务逻辑(如支付、订单、登录)?哪些是边缘报表或已废弃功能?
- 测试覆盖:有单元测试吗?有手动测试用例吗?完全没有测试的代码风险最高。
- 团队能力:当前团队是否熟悉老版本 PHP 特性(如 MySQLi 与 PDO 的差异)?
第二阶段:制定处理策略(从“动不了”到“能动”的路线图)
根据评估结果,选择以下三种策略之一或混合使用:
策略 A:原地重构(逐步现代化)—— 最推荐
适用场景:业务逻辑复杂、无单测、但还想维护和迭代的项目。
- 引入静态代码分析:先在 CI/CD 中集成 PHPStan,从 level 0 开始,逐步修复到 level 6+。
- 建立安全围栏:
- 全局启用
strict_types=1(至少在新文件上)。 - 对所有输入(
$_GET,$_POST,$_COOKIE)进行强制类型转换或白名单过滤。 - 将
mysql_*统一替换为 PDO + 预处理(这是安全底线,必须最快完成)。
- 全局启用
- 拆分巨石:
- 从纯数据层开始,将数据库查询封装成 Repository 类。
- 慢慢剥离业务逻辑,往 Service 层移动。
- 不要一次性改所有:用 “绞杀者模式”,每次只改造一个模块,并行运行新旧两套代码,对比结果。
- 引入现代特性:
- 在旧代码中逐步 使用 Composer 自动加载(即使是手动
require的类,也慢慢迁移到命名空间)。 - 旧文件顶部加
use语句,逐步去掉global $db。
- 在旧代码中逐步 使用 Composer 自动加载(即使是手动
策略 B:容器化隔离(保持原样,封装运行)—— 最保守
适用场景:仅用来维护,不再开发新功能,或替换成本极高(如与硬件/第三方 API 强绑定)。
- Dockerize:将项目用 Docker 打包,镜像内运行指定旧版本 PHP(如 5.6),并打上安全补丁(如 php5.6-fpm-alpine)。
- 反向代理隔离:用 Nginx 将旧项目限制在
/legacy路径下,新功能在新框架(Laravel/Symfony)上开发,通过 HTTP 请求或消息队列(RabbitMQ)与旧系统交互。 - 安全增强:
- Web 服务器层面:强制
Content-Security-Policy、X-Frame-Options。 - 数据库层面:限制老代码使用的数据库账户权限(只允许 CRUD,禁用 DDL)。
- Web 服务器层面:强制
策略 C:重写(推倒重来)—— 风险最高,慎重
适用场景:版本太老(PHP 4)、无文档、无测试、业务逻辑已不可理解且无人维护。
- 必须做:先用旧系统实现一个完整的 API 层(如 JSON API),新系统通过 API 调用旧系统。
- 逐步替换:每重写一个功能,就废弃旧 API 端点。
- 数据迁移:不要试图一步迁完,用双写(新旧数据库同时写入)或停机迁移方案。
第三阶段:执行与工具链(硬仗怎么打)
务必先做安全加固(最快见效)
- 替换
ext/mysql:这是 PHP 5.5 已废弃、7.0 已移除的扩展,必须全部改为 PDO。 - 禁止
eval():在代码审查中列为 不可通过项。 - 修复文件包含:
include($user_input)改为白名单映射。 - 密码哈希:
md5($pass)改为password_hash(),并兼容旧密码(通过password_needs_rehash())。
自动化测试是安全网(先写后改)
- 从 “冒烟测试” 开始:手工测试最核心的 10 个业务流程,记录下来。
- 覆盖 CRUD:用 PHPUnit 或 Codeception 为每个数据库查询写测试(至少测试连接成功和结果类型)。
- 错误处理:旧代码常 抑制错误,改为
try-catch+ PDO::ERRMODE_EXCEPTION。
严格代码审查
- 设置 “红线”:不允许出现
extract(),register_globals(如果还开的话),不允许直接拼接 SQL。 - 渐进式类型:对函数参数
function getUser(int $id, string $name)加类型声明,即使旧代码没类型,也尽量加文档注释@param。
性能与维护性改进(非必须但推荐)
- 启用 OpCache:PHP 7+ 默认有,PHP 5.5+ 可配置,显著提升性能。
- 统一错误处理:旧代码常
trigger_error(),die()混合用,统一为throw new \RuntimeException()。 - 日志系统:将老旧的
error_log或echo $msg替换成 Monolog,方便排查。
第四阶段:长期维护与防范(别让新代码变遗产)
- 新功能必须用现代实践:规定从某一天起,所有新功能必须用 PSR-4 命名空间、强制类型、PDO,老代码允许保留,但改动的部分需达标。
- 建立“技术债墙”:每次修复一个旧 bug,必须有一个对应的小重构或加一个测试。
- 定期评审:每季度用 PHPStan 扫描一次,看 tech debt 指数是否在下降。
- 团队文档:写一份《老项目维护指南》,记录常见的坑(如:这里用了
session_register(), 那里依赖了magic_quotes)。
投入产出比建议(优先级排序)
| 优先级 | 动作 | 投入产出比 |
|---|---|---|
| P0 | 替换 mysql_* 为 PDO,修复 SQL 注入 |
极高(安全漏洞风险立刻消除) |
| P1 | 禁用 eval、extract、register_globals |
高(消除逻辑隐患) |
| P2 | 为所有核心路径写冒烟测试 | 中(防止改错后线上崩溃) |
| P3 | 将 Composer 自动加载引入,逐步命名空间化 | 中(提升可维护性) |
| P4 | 拆分巨石类(如 God Object) | 低(需要大量确定性测试才能安全做) |
最后提醒:不要试图一次搞定
处理遗留代码的核心是 “让坏代码不能变坏,而让好代码能覆盖坏代码”,建议先花 20% 时间做安全加固和测试,剩下 80% 时间用“绞杀者模式”慢慢抽离,短期目标:线上不出事故;中期目标:能安全加功能;长期目标:能安全重构整个系统。