Skip to content
// 0x
Go back
0x13 // 后端实践

无框架 PHP 项目的渐进式工程化改造

背景

接手了一个 PHP 营销站点。文件结构是典型的”上古 PHP”风格:

├── config.php
├── index.php
├── integrations.php
├── policy.php
├── tor.php
├── includes/
│   ├── nav.php
│   └── footer.php
├── lang/
├── styles.css
└── script.js

所有文件平铺在根目录,config.phprequire 引入,_t() 函数全局定义,CSS/JS 混在 PHP 目录中。没有命名空间、没有自动加载、没有依赖管理。

需要新功能(Stripe 支付、定价页面),直接在根目录加 PHP 文件就行的时代过去了。但引入 Laravel 或 Symfony 又像用牛刀杀鸡——迁移成本高,现有功能可能被破坏。

目标是:渐进式改造,每一步都可逆,不中断现有页面。

第一步:Composer + PSR-4 自动加载

这是投入产出比最高的一步。创建 composer.json,加上 PSR-4 映射:

{
    "require": {
        "vlucas/phpdotenv": "^5.6",
        "stripe/stripe-php": "^16"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}

把环境配置抽到 src/Config.php

namespace App;

class Config
{
    public static function string(string $key, string $default = ''): string
    {
        return (string)($_ENV[$key] ?? $default);
    }

    public static function stripeSecretKey(): string
    {
        return static::string('STRIPE_SECRET_KEY');
    }
}

修改 config.php——加三行,改不动的保留:

require_once __DIR__ . '/vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->safeLoad();

// 以下代码完全不动
session_start();
$lang = ...
$translations = require __DIR__ . "/lang/$lang.php";
function _t($key) { ... }

关键是 safeLoad() 而不是 load()——.env 文件不存在时不崩溃,开发环境友好。

第二步:public/ 入口隔离

把可公开访问的文件迁入 public/,不可公开的留在上级:

├── public/
│   ├── index.php
│   ├── integrations.php
│   ├── styles.css
│   └── script.js
├── src/
├── lang/
├── templates/
├── vendor/
├── config.php
└── .env

同时修改 nginx 根目录指向 public/

root /var/www/html/public;

这一步风险最低——只是文件搬家和 nginx 配置调整,不涉及业务逻辑。用了 git mv 保留文件历史,回滚也只需改回 nginx 配置。

第三步:视图层分离

原有 includes/ 改名 templates/,提取页面内容区域为独立模板文件:

templates/
├── nav.php
├── footer.php
├── home.php         ← 原 index.php 的 <main> 内容
├── integrations.php
├── policy.php
└── tor.php

入口文件瘦身为控制器:

// public/index.php
<?php require __DIR__ . '/../config.php'; ?>
<!DOCTYPE html>
<html>
<head>
  <title><?= _t('home_page_title') ?></title>
  <link rel="stylesheet" href="assets/styles.css">
</head>
<body>
  <?php require __DIR__ . '/../templates/nav.php'; ?>
  <?php require __DIR__ . '/../templates/home.php'; ?>
  <?php require __DIR__ . '/../templates/footer.php'; ?>
  <script src="assets/script.js"></script>
</body>
</html>

提取边界很清楚:<main> 内的内容属于模板,<head><script> 留在入口。每个页面的 <head> 差异较大(OG 标签、JSON-LD 结构),不适合强行抽象 layout。

第四步:加入新功能(Stripe 支付)

有了以上结构,加新功能变成纯增量工作:

src/Payment/Checkout.php     ← Stripe 服务类
public/create-session.php    ← 创建支付会话
public/webhook.php           ← 支付回调
public/success.php           ← 成功页
public/cancel.php            ← 取消页

每个新文件职责单一。create-session.php 只需要 20 行:

<?php
require __DIR__ . '/../config.php';

$checkout = new \App\Payment\Checkout();
$result = $checkout->createSession($planId, $successUrl, $cancelUrl);
echo json_encode($result);

没有路由注册、没有依赖注入配置、没有服务提供者。够用,且一眼能看出逻辑。

第五步:知识库生成脚本

为了给 AI 客服提供数据,需要把网站内容(定价、功能、FAQ)提取为结构化文档。写一个 Python 脚本,直接解析 PHP 翻译文件:

import re

def parse_php_array(path):
    with open(path, encoding='utf-8') as f:
        content = f.read()
    result = {}
    for m in re.finditer(r"'([^']+)'\s*=>\s*'((?:[^'\\]|\\.)*)'", content):
        key, value = m.group(1), m.group(2)
        result[key] = value.replace("\\'", "'")
    return result

脚本输出双语 markdown 文件,同时被 RAG 系统的 watcher 自动检测并索引。

为什么不直接上框架

因素Laravel本项目方案
迁移成本需重写全部路由和控制器零,现有页面不改
学习曲线需熟悉 Artisan/ORM/Blade无,纯 PHP
构建体积~30MB~5MB(含 vendor)
后续可扩展丰富够用,可随时升级

关键在于:这个站点不会演变为复杂的后台系统。它的本质是内容展示 + 支付转化,真正的业务逻辑在另一个独立应用中。为这样的站点上框架是过度工程。

成果

改造后的项目结构:

├── public/
│   ├── index.php
│   ├── integrations.php
│   ├── pricing.php
│   ├── success.php / cancel.php
│   ├── create-session.php
│   ├── webhook.php
│   └── assets/
│       ├── styles.css
│       ├── chat.css / chat.js
│       └── logo.svg
├── templates/
│   ├── nav.php / footer.php
│   ├── home.php
│   ├── integrations.php
│   ├── pricing.php
│   └── policy.php / tor.php
├── src/
│   ├── Config.php
│   └── Payment/
│       └── Checkout.php
├── lang/
├── config.php
├── composer.json
└── .env

所有现有页面保持 200 OK,新增的 Stripe 支付和 AI 客服零侵入接入。

小结

PHP 项目的工程化改造不一定非要上框架。Composer + PSR-4 自动加载提供了代码组织能力,public/ 隔离保障了安全,视图模板消除了页面重复。

这套模式适用于大量”内容展示为主、少量交互功能”的 PHP 站点——用最小成本换取长期可维护性。当有一天复杂度真的超出了这套模式的容纳能力时,再考虑框架也不迟。


Share this post on:

Previous Post
生产服务器备份审计与体系建设 — 从 5 台零轮转到全线 7 天保留
Next Post
RAG + 本地/在线 LLM:一个可离线的 AI 客服架构