PHP 与 AI 协同编程实战:论 LLM 如何在处理 PHP 遗留系统迁移中自动识别重构热点

各位老铁,大家好!

欢迎来到今天的“PHP 灵魂修复”讲座。我是你们的主讲人,一个在代码堆里刨食了十年的“代码考古学家”。

今天我们要聊点硬核的,但也得聊聊点好玩的。想象一下,你接手了一个项目。打开项目文件夹,你看到的是这样的结构:一个名为 index.php 的文件,里面甚至能找到 2009 年的代码,长得比你奶奶的裹脚布还长,逻辑之曲折,堪比马尔代夫的海沟。这就是我们的战场——PHP 遗留系统

在这个战场上,充满了“上帝类”、魔法数字、隐形的全局变量,以及一坨坨像意大利面一样纠缠不清的 if-else。传统的重构?不存在的。手动重构?那是找死。稍有不慎,把生产环境的数据库给删了,你就准备好在那儿吃土了。

但是!时代变了。现在轮到 AI(大语言模型) 登场了。今天,我们不谈虚的,我们实战演练:如何让 LLM 像个拥有 10 年经验的资深架构师一样,帮你自动识别 PHP 遗留代码中的“病灶”,并开刀动手术。

准备好了吗?让我们把键盘敲得噼里啪啦响!


第一章:战场侦察——AI 如何充当“代码侦探”

首先,我们必须承认,旧代码有一种魔力,它能让你在半夜三点对着屏幕发呆,问自己“我为什么要学计算机”。

AI 最大的优势在于什么?在于它不累,也不怕重复劳动。它不会抱怨“这代码写得像屎一样”,它只会默默地读下去。

实战场景 1:识别“上帝类”与过长的函数

在 PHP 遗留系统中,最常见的就是一个 UserController.php,里面既管注册,又管登录,还管发送邮件,甚至还在里面写了一段简单的 SQL。这就是典型的“上帝类”。

假设我们有一段这样的代码(PHP 7.4 风格,甚至更早):

// BadController.php - 一个典型的“地狱控制器”
class OrderController {
    public function processCheckout($userId, $items, $paymentMethod) {
        // 1. 查询用户
        $user = $this->db->query("SELECT * FROM users WHERE id = " . (int)$userId);
        if (!$user) return false;

        // 2. 验证库存(硬编码逻辑)
        foreach ($items as $item) {
            $stock = $this->db->query("SELECT stock FROM products WHERE id = " . (int)$item['id'])->fetchColumn();
            if ($stock < $item['qty']) {
                throw new Exception("库存不足: " . $item['name']);
            }
        }

        // 3. 扣减库存
        foreach ($items as $item) {
            $this->db->exec("UPDATE products SET stock = stock - " . (int)$item['qty'] . " WHERE id = " . (int)$item['id']);
        }

        // 4. 扣钱(又是硬编码)
        $paymentGateway = new PaymentGateway();
        if (!$paymentGateway->charge($user['card'], 100.00)) {
            // 回滚库存
            return false;
        }

        // 5. 记录日志
        $this->logger->log("Order processed for " . $userId);
        return true;
    }
}

看到这坨代码,你是不是想打人?这就是我们要识别的“热点”。如果我们要手动改,得一个个去拆。但如果你问 LLM 呢?

AI 介入(Prompt Engineering 实战):

你是一位资深 PHP 架构师。请分析以下代码,识别出其中的代码坏味道。
1. 函数是否过长?
2. 是否包含了过多的职责(上帝类特征)?
3. 是否存在硬编码?
4. SQL 查询是否安全?

请给出具体的重构建议,并指出哪些部分应该提取为独立的服务或对象。

AI 的分析报告(模拟):

分析结果:

  1. 职责混乱: processCheckout 方法同时处理了用户验证、库存检查、数据库事务管理、支付网关调用和日志记录。
  2. 硬编码: 库存检查和扣减逻辑散落在循环中;支付金额 100.00 也是硬编码。
  3. 可测试性差: 硬依赖数据库和 PaymentGateway,无法单独测试逻辑。

重构建议:

  1. 将业务逻辑提取到 OrderService 中。
  2. 引入 InventoryRepository 封装数据库操作。
  3. 引入 PaymentService 封装支付逻辑。
  4. 使用 PHP 8.0+ 的构造器属性提升和联合类型。

看,AI 一眼就看穿了你的代码内衣。这就是 LLM 作为“热识别雷达”的价值。它不需要你读它,它读你。


第二章:手术刀——自动类型化与现代化

PHP 8.x 带来了很多新特性,比如构造器属性提升、联合类型、Match 表达式等。这些特性能让我们把代码变短,变强。

但是,遗留代码往往是 PHP 5.6 或 7.0 写的,全是 var,全是 public function foo($a, $b),没有类型提示,字符串和数字混在一起。

实战场景 2:从“魔术师”到“数学家”

遗留代码中充满了这种“魔术数字”和隐式类型转换:

function calculateDiscount($price, $type) {
    if ($type == 'VIP') {
        return $price * 0.8;
    } elseif ($type == 'GOLD') {
        return $price * 0.9;
    } else {
        return $price; // 默认不打折
    }
}

这段代码写起来没问题,但如果是一个 5000 行的项目里到处都是这种 if ($type == 'VIP'),维护起来简直是灾难。万一有一天折扣变成了 7.5%,你得遍历全项目替换。

现在,我们让 AI 来帮忙。

AI 介入:

请将以下 PHP 函数重构为 PHP 8.2 标准代码:
1. 定义一个枚举(Enum)来替代字符串 'VIP', 'GOLD'。
2. 使用构造器属性提升定义类结构。
3. 使用 Match 表达式替代 if-else。
4. 添加严格的类型声明。

AI 生成的“黄金代码”:

enum DiscountType: string
{
    case VIP = 'VIP';
    case GOLD = 'GOLD';
    case STANDARD = 'STANDARD';
}

class PricingService
{
    public function calculateDiscount(Price $price, DiscountType $type): Price
    {
        return match ($type) {
            DiscountType::VIP => $price * 0.8,
            DiscountType::GOLD => $price * 0.9,
            DiscountType::STANDARD => $price,
        };
    }
}

代码对比:

  • 旧代码: 10 行,依赖字符串比较,容易拼写错误。
  • 新代码: 封装在 Enum 和 Service 类中,类型安全,IDE 自动补全,清晰明了。

这就是“重构热点”识别的一个层面:消除技术债务中的“松耦合”部分。AI 帮你把散落在各处的魔法字符串变成了强类型的枚举。


第三章:安全审计——AI 眼中的 SQL 注入漏洞

PHP 遗留系统最大的噩梦之一就是 SQL 注入。很多老代码还在用这种写法:

// 看着就让人手抖
public function getUser($username) {
    $sql = "SELECT * FROM users WHERE username = '" . $username . "'";
    $result = $this->db->query($sql);
    return $result->fetch(PDO::FETCH_ASSOC);
}

虽然现在有 PDO,但很多老项目还在用 mysql_query 或者直接拼接。这是必须修复的“高危热点”。

实战场景 3:自动安全审计

我们不需要人工去一行行读 SQL,我们可以让 AI 做一个“代码扫描器”。

AI 介入:

请扫描以下代码片段,检查是否存在 SQL 注入风险。
如果发现拼接 SQL 字符串的情况,请指出具体的行,并给出使用 PDO 预处理语句的安全重写代码。

AI 的反馈:

[安全警报]
位置: BadAuth.php 第 15 行。
风险等级: 极高。
问题描述: $sql 字符串直接拼接了用户输入 $username,攻击者可以通过输入 ' OR '1'='1 等恶意 payload 绕过认证。

修复代码:

// 安全版本
public function getUser(string $username): ?array
{
    $sql = "SELECT * FROM users WHERE username = :username LIMIT 1";
    $stmt = $this->pdo->prepare($sql);
    $stmt->execute(['username' => $username]);
    return $stmt->fetch(PDO::FETCH_ASSOC);
}

AI 在这里的作用不仅是识别,更是教育。它告诉你为什么这样写不对,以及为什么那样写是对的。这比你找了个不懂技术的外包,还要强一万倍。


第四章:测试驱动——无测试代码的噩梦

“我不写测试,因为代码很简单。”
“等上线出了 Bug 再修吧。”
这是所有 PHP 老项目都在说的鬼话。

没有测试,重构就是赌博。但有了 LLM,我们可以做一个逆向过程:先让 AI 写测试,然后基于测试重构。

实战场景 4:为混沌代码生成 PHPUnit

假设我们接手了一个不知道怎么跑的 EmailSender 类,里面全是 echodie,没有返回值。我们要重构它,但不敢动。

AI 介入:

以下是一个功能未知的 PHP 类。请:
1. 分析它的可能行为。
2. 为这个类生成一套完整的 PHPUnit 测试用例。
3. 包含正常情况和异常情况。

AI 生成的测试代码:

class EmailSenderTest extends TestCase
{
    public function testSendEmailSuccessfully()
    {
        // Mock the dependency
        $mockLogger = $this->createMock(LoggerInterface::class);
        $sender = new EmailSender($mockLogger);

        $result = $sender->send("[email protected]", "Hello", "Content");

        // 预期行为:应该返回 true
        $this->assertTrue($result);
    }

    public function testSendEmailWithInvalidAddress()
    {
        $mockLogger = $this->createMock(LoggerInterface::class);
        $sender = new EmailSender($mockLogger);

        // 预期行为:非法邮箱应该返回 false
        $this->assertFalse($sender->send("invalid-email", "Hello", "Content"));
    }
}

有了这些测试,你就有了护身符。现在,你可以大刀阔斧地重构了。改完代码,跑一下测试,全绿了,你就赢了。这就是 AI 给你带来的安全感。


第五章:实战演练——一个完整的重构流水线

好了,理论讲累了。我们来搞个大项目:重构一个老旧的 CMS 文章管理模块。

背景:
一个名为 ArticleController.php 的文件,500 行。它直接从 $_POST 读取数据,存入 JSON 文件(你没看错,存 JSON 文件),没有任何验证。

第一步:请求 AI 进行“代码解剖”

你是一个代码审查员。请阅读 ArticleController.php 的前 100 行。
1. 列出 5 个最严重的代码坏味道。
2. 指出数据流向。
3. 建议一个合理的 MVC 重构方案。

AI 的反馈:

  1. 直接操作 $_POST,不可测试。
  2. 数据验证逻辑缺失。
  3. 建议拆分为:ArticleValidator(验证),ArticleRepository(持久化,改为数据库),ArticleController(路由)。

第二步:代码生成

我们直接让 AI 帮我们写新的代码。

AI 生成新的 Controller(简化版):

use AppServicesArticleService;
use AppValidationArticleValidator;

class ArticleController
{
    public function __construct(
        private ArticleService $service,
        private ArticleValidator $validator
    ) {}

    public function store(Request $request): JsonResponse
    {
        $data = $request->getParsedBody();

        // 使用 Validator
        if (!$this->validator->validate($data)) {
            return new JsonResponse(['errors' => $this->validator->getErrors()], 400);
        }

        try {
            $article = $this->service->create($data);
            return new JsonResponse($article, 201);
        } catch (Exception $e) {
            return new JsonResponse(['error' => 'Server Error'], 500);
        }
    }
}

第三步:填补空白

AI 生成了 Controller,但 Service 和 Validator 还没写。这时候,你可以利用 AI 的“上下文记忆”能力,或者直接复制粘贴之前的对话。

请根据上面 Controller 的调用,帮我生成 ArticleService 和 ArticleValidator 的代码。
要求:
1. ArticleValidator 必须使用 PHP 8.1 的 Enum 和闭包验证。
2. ArticleService 必须使用 Doctrine ORM。

AI 会顺滑地给你生成:

  • 一个包含 Length, Email, Required 规则的 Validator。
  • 一个使用 EntityManager 进行 CRUD 操作的 Service。

第四步:验证

把新代码丢进 Docker 容器,跑一下测试。如果报错,把报错日志扔给 AI:“这行报错了,为什么?” AI 会像哄孩子一样告诉你:“哦,你漏传了必填参数 title。”


第六章:避坑指南——AI 也会撒谎

虽然 AI 很强,但它是模型,不是神。在重构过程中,你要警惕它的“幻觉”。

1. 环境差异
AI 不知道你的项目结构。它生成的 use AppServices... 可能根本不存在。你需要做的是导航员,而不是只负责输入指令的懒汉。

2. 业务逻辑的黑盒
AI 擅长写漂亮的代码,但不擅长懂复杂的业务规则。比如:“这个折扣逻辑是为了配合十年前的营销活动写的,虽然看着很蠢,但必须保留。” 这种逻辑,AI 改了,你会被老板骂死。

3. 第三方库的坑
如果代码里用了一个很冷门的 Composer 包,AI 可能不知道它的最佳实践。这时候,你需要自己查阅文档,或者让 AI 帮你查文档。

实战技巧:
不要直接让 AI 重写整个文件。那太危险了。
正确的姿势是:

  • 让 AI 生成单个函数的重写。
  • 让 AI 生成单元测试
  • 让 AI 生成迁移脚本

第七章:未来展望——AI 是 Copilot,不是 Autopilot

各位老铁,技术是为人服务的。

在 PHP 遗留系统迁移中,LLM 的角色不是那个坐在驾驶座上握着方向盘的“自动驾驶仪”,它更像是一个经验丰富的副驾驶

  • 它能帮你把那些长得像迷宫一样的代码指出来。
  • 它能帮你把那些危险的 SQL 注入漏洞指出来。
  • 它能帮你把那些废弃的 PHP 5 语法翻译成现代的 PHP 8 语法。

但是,方向盘得你自己握。这趟旅程的终点——系统架构的升级、业务逻辑的优化——得靠你自己思考。

最后的实战代码:

为了庆祝我们今天的讲座,我们来写一段“元代码”。这段代码会告诉 AI 如何帮助重构它自己。

<?php

/**
 * 神秘的遗留代码重构助手
 * 请 AI 分析此文件的代码坏味道,并使用 PHP 8.2 特性重构它。
 * 
 * @author PHP 侦探
 * @version Legacy 1.0
 */

function processLegacyData($data) {
    $res = array();
    foreach ($data as $key => $val) {
        // 这里的 if 判断极其脆弱
        if ($key == 'username') {
            $res['user'] = $val;
        } elseif ($key == 'email') {
            $res['contact'] = $val;
        } else {
            $res['extra'][] = $val;
        }
    }
    return json_encode($res);
}

// 当你把这个扔给 ChatGPT/Claude 时,期待它变成这样:
// class DataTransformer {
//     private array $mappings = ['username' => 'user', 'email' => 'contact'];
//
//     public function transform(array $data): string {
//         return $this->toJson($data);
//     }
// }

看到了吗?这就是我们追求的目标。

不要害怕旧代码,它们只是睡着了。拿起你的键盘,召唤你的 AI 助手,让我们一起把这些沉睡的代码唤醒,让它们穿上 PHP 8 的新西装,去迎接新的互联网时代!

如果你在重构过程中遇到了什么奇葩 Bug,记得回来找我,咱们再喝杯咖啡,聊聊代码。散会!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注