遗留 PHP → PHP 8.3/8.4 现代化工具
何时使用本工具
当遇到一个必须满足以下条件的遗留 PHP 项目(通常是针对 PHP 5.x 编写的自定义 CMS)时,应使用此技能:
- 无需完全重写即可在 PHP 8.3 / 8.4 上运行,且/或
- 必须部署到 共享主机(cPanel/Plesk) 且不使用 Docker,并通过 FTP 进行。
典型特征:mysql_*、ereg/eregi、<? 短标签、PHP4 风格的构造函数、
裸词数组键 ($row[Id])、$$var['key']、sizeof(null)、MyISAM 导出、
TemplatePower 模板、config/defines*.php 中的明文凭据。
本技能提供的资产
| 资产 | 用途 |
|---|---|
scripts/compat.php | 兼容层:mysql_*→mysqli,ereg*/split→preg,userErrorHandler |
scripts/fix_barewords.php | 基于 Tokenizer 的安全裸词数组键引号化 |
scripts/fix_variable_variables.php | 修复 PHP 8 $$var['key'] 统一变量语法破坏 |
scripts/check_signatures.php | 静态 LSP 方法签名检查器 |
scripts/lint-all.sh | php -l 全面检查 |
scripts/smoke-test.sh | URL 列表的 HTTP 冒烟测试 |
templates/defines.php | .env 加载器 + 数据库常量(兼容有无 Docker 环境) |
templates/env.example | .env 模板(DB_* + FTP_*) |
templates/htaccess | 现代 Apache/PHP-FPM .htaccess |
templates/user.ini | 适用于共享主机的 PHP-FPM 覆盖配置 |
templates/timthumb.php | 安全的 GD 缩略图生成器(替换有漏洞的 TimThumb) |
deploy/deploy.sh | FTP 或 rsync 部署;排除开发文件;写入远程 .env |
黄金法则
- 安全第一:在进行任何操作前,先
git init并提交基线版本。备份数据库。 - 先解除阻塞再重构:兼容层能立即让整个代码库运行起来。
- 一个中心数据库层:首先替换/限制
dbClass;兼容层会覆盖原始的mysql_*调用。 - 尽早进行安全加固:一旦站点能渲染,立即移除危险工具和 TimThumb。
- 持续验证:每批修改后执行
php -l,然后对所有 URL 进行冒烟测试。
操作步骤
0. 建立基线
git init -q && git add -A && git commit -qm "baseline: legacy PHP"
find . -name '*.php' -not -path './node_modules/*' | wc -l
保存一份真实的公开 URL 列表(从数据库/站点地图获取),用于 scripts/smoke-test.sh。
1. 安装兼容层 shim
- 将
scripts/compat.php复制到比如includes/compat.php。 - 在引导文件的最顶部,在任何遗留代码之前加载它:
include_once(__DIR__.'/../includes/compat.php'); - 它提供了
mysql_*、ereg/eregi/ereg_replace/eregi_replace/split、mysqli_report(MYSQLI_REPORT_OFF)(SQL 错误返回false而非抛出异常)、 以及userErrorHandler()(抑制 PHP 5 时代旧的 E_NOTICE "未定义索引/变量")。 - 注册一次处理程序:
if (function_exists('userErrorHandler')) set_error_handler('userErrorHandler');
2. 机械修复(使用脚本)
php scripts/fix_barewords.php # 试运行
php scripts/fix_barewords.php --apply
php scripts/fix_variable_variables.php # 试运行
php scripts/fix_variable_variables.php --apply
scripts/lint-all.sh
然后修复 lint 仍然报告的问题:
- 短标签:
<?→<?php(grep<\(?!php|=|xml))。 - PHP4 构造函数:
function ClassName()→function __construct()。 - 花括号字符串偏移:
$s{0}→$s[0]。 - 循环/switch 外的游离
break(PHP 8 中为致命错误)。 ${...}中的可变变量:引用裸词 / 使用${$var['k']}。
3. 签名、静态与空安全
php scripts/check_signatures.php
对于每个 MISMATCH,给子方法添加默认参数(例如 $tabla = null)。
然后在运行时修复出现的致命错误:
| 错误 | 修复 |
|---|---|
ArgumentCountError: too few arguments | 为尾部参数提供默认值 |
静态方法中的 $this | 使用 self::/static:: 或传递应用上下文 |
method_exists(null, ...) | is_object($x) && method_exists($x, ...) |
Cannot access offset of type string on string | 在 $x['k'] 之前先检查 is_array($x) |
Undefined constant "X" | 引用裸词键 / 常量 |
sizeof/count/array_keys(null) | is_array($x) ? ... : ..., (array)$x |
foreach(null) | foreach ((array)$x as ...) |
Accessing static property X as non static | $this->prop → self::$prop |
4. 针对真实数据库运行并迭代
将配置指向数据库,然后请求每个 URL。每次测试修复一个致命错误,直到
scripts/smoke-test.sh 报告零失败且日志中没有警告。
5. 安全加固(尽早进行)
- 用
templates/timthumb.php替换 TimThumb。 - 删除
scanner.php、chmod777.php、info.php、多余的调试脚本。 - 配置
.htaccess拒绝访问.env、.git、config/defines.php、点文件。 - 轮换已提交的数据库密码;将机密信息移至
.env。 - 建议添加 CSRF 令牌、CSP/HSTS、限速、预处理语句。
6. 配置 + 部署
- 使用
templates/defines.php(.env加载器)和templates/env.example。 - 使用
templates/htaccess和templates/user.ini。 - 使用
deploy/deploy.sh进行部署。
.env 系统
config/defines.php 从项目根目录加载 .env,回退到 getenv()
最后使用安全默认值。这保持了 Docker 的兼容性(相同的 DB_* 键),同时
使得 FTP 部署只需要编辑一个文件。
.env(永远不要提交):
DB_HOST=localhost
DB_NAME=cpaneluser_termospa
DB_USER=cpaneluser_termospa
DB_PASS=super-secret
DB_SUBDIRECTORIO=
部署(无 Docker)
共享主机(cPanel/Plesk)检查清单
- PHP:选择 8.3(或 8.1+);启用
mysqli、gd、mbstring、pdo_mysql。 - 数据库:创建数据库 + 用户,导入数据导出文件。对于 MySQL 8:
预料到SET SESSION sql_mode='NO_ENGINE_SUBSTITUTION';utf8_spanish_ci→utf8mb3_spanish_ci的警告(或执行 utf8mb4 迁移)。 - 文件:上传到
public_html/(排除docker/、tools/、router.php、_DOCS/、.git/)。 - 权限:目录
755,文件644;recursos/cache/、.cache/、upload/需要可写(775/777)。 - 配置:创建远程
.env(deploy.sh 会执行此操作)并验证DB_*。 - 在
.htaccess中拒绝访问.env。
deploy.sh
./deploy.sh ftp # lftp 反向镜像
./deploy.sh ftp --dry-run
./deploy.sh rsync # 通过 SSH 的 rsync
它会读取 .env(FTP_HOST/USER/PASS/PATH/TLS 或 SSH_*),仅暂存运行时
文件,写入一个仅远程使用的 .env(仅包含 DB_*,因此 FTP 凭据永远不会
落在服务器上),上传,并打印权限/数据库操作步骤。
验证检查清单
-
scripts/lint-all.sh→ 全部通过 -
scripts/smoke-test.sh urls.txt→ 0 失败 - 首页 / 产品 / 项目 / 新闻 / 联系页面正常渲染
-
/sitemap.xml和/rss返回有效的 XML -
/imagenes/thumbnail/...返回一张图片(GD + 重写) -
/administracion/login/加载;登录 + 内容管理器正常工作 - 联系表单提交成功
- 管理员图片上传写入
upload/ - 错误日志中没有
PHP Warning/Fatal
总是会坑到你的陷阱
- 默认情况下,PHP 8.1+ 中的
mysqli_query会抛出异常;调用mysqli_report(MYSQLI_REPORT_OFF)以保持旧的or die()/if($q=...)模式继续工作。 .htaccess中的php_flag/php_value在 PHP-FPM 下会导致 HTTP 500 — 请使用.user.ini。- PHP 8 的统一变量语法会静默破坏
$$var['key'](写入错误的变量)— 请运行fix_variable_variables.php。 include()的相对路径相对于脚本目录/当前工作目录解析;推荐使用__DIR__。- PHP 8 中已移除:
mysql_*、ereg*、split、create_function、each、魔术引号。 - OPcache 会缓存旧文件:在编辑后重启 FPM/Apache。
- UTF-8:使用
SET NAMES utf8mb4;新项目推荐使用 InnoDB +utf8mb4_unicode_ci。
同类项目
这些 CMS 项目共享相同的架构。执行相同的过程,仅需调整
引导文件路径和缩略图重写目标。将 scripts/ 复制到目标项目的
tools/ 中,从项目根目录运行。