编程 PHP 5.6 升 8.x:跳级就是引爆

2026-08-27 21:06:48 views 11

PHP 5.6 升 8.x:跳级就是引爆

先下结论:PHP 5.6 直接升 8.x 是一条不归路。7.0 砍 mysql_*、7.2 移除 each/create_function、8.0 收紧类型系统,三波破坏性变更叠到一起,等于一次引爆,结果是排错范围无限扩大,代价指数级上升。

正确路线只有一个:5.6 → 7.2 → 7.4 → 8.0 → 8.1/8.2。每级至少跑一轮静态扫描和测试,确认无 E_DEPRECATED 再进下一级。

先选路径,别直接动手

三种常见选型:

  • 大爆炸重写:仅适合代码量 2 万行以下、测试覆盖 80% 以上、业务可以整体停摆的小项目。风险集中,回滚机会基本没有。
  • 逐级就地升级:适合大多数遗留系统。每步可运行、可回滚,是这篇文章要讲的主线路。
  • Strangler 绞杀者:几十万行 ERP/后台,业务不能停。边跑边替换,周期长但安全。

迁移主线

资产盘点

  • php -m 核对扩展,确认目标版本对应包名(如 php8.2-redis)。
  • 检查 composer.json 的 php 约束和框架版本,TP5.1 以下、Laravel 8 以下官方不支持 8.x。
  • 全局搜高危函数:mysql_eregeach(create_function(mcrypt___autoload
  • 备份 composer.lock、数据库、php.ini。

双运行时分流

多版本 php-fpm 并存,Nginx 按 location 切换:

location /api/new/ { fastcgi_pass php82-fpm:9000; }
location /          { fastcgi_pass php56-fpm:9000; }

这个配置既是灰度入口,也是回滚开关——改 upstream 30 秒切回。

静态扫描

composer global require squizlabs/php_codesniffer phpcompatibility/php-compatibility
phpcs --standard=PHPCompatibility --runtime-set testVersion 8.2 src/

error 必须清零,warning 最晚到 7.4 阶段清完。php -l 只验语法,查兼容性还得靠 PHPCompatibility。

逐级改

  • 7.2:mysql_* → PDO,ereg → preg,__autoload → spl_autoload_register。
  • 7.4:each() → foreach,create_function() → 箭头函数,json_decode 第二参数禁止传 null。
  • 8.0:处理 TypeError、隐式转换、parse_str 必须带第二参数、mktime() 至少 1 参。
  • 8.1/8.2:readonly 冲突、动态属性弃用、内部方法重写需要兼容返回类型。

每级都开 E_ALL + E_DEPRECATED,不允许带 warning 进下一级。

重装依赖

rm -rf vendor composer.lock
composer install
composer check-platform-reqs

注意是 install 不是 update。依赖解决失败时用 composer why-not php 8.2 定位是哪只包在拦路。

测试与灰度

PHPUnit 升 9/10,老用例里用 == 比较数字字符串的会挂。先放 1% 流量跑两周,盯 php-fpm.log 和业务错误日志,再逐步放量。收尾删除 5.6-fpm,开 OPcache 与 JIT,date.timezone 写死 Asia/Shanghai。

必炸清单

按爆炸顺序排:

  1. mysql_*:7.0 起调用即报 Call to undefined function。替换成 PDO 时注意:拼接 SQL 不改成占位符等于没改。
  2. each / create_function / ereg / mcrypt:直接移除。mcrypt → openssl 时 IV 和填充逻辑不同,老数据需写兼容层。
  3. 松散比较语义反转"123a" == 123 在 5.6/7.x 为 true,8.x 为 false,业务分支静默走反,不报错,最阴险。
  4. 类型约束生效function add(int $a)"20" 在 8.x 直接抛 TypeError,老代码里全是这种隐式依赖。
  5. parse_str:不传第二参数直接报错;$row['name'][0] 对非字符串也抛 TypeError,先判断类型。
  6. 动态属性弃用:未声明属性赋值在 8.1 起被弃用。加类型声明,或临时用 #[AllowDynamicProperties]
  7. Serializable 弃用:改 __serialize/__unserialize;老 Session 里的旧对象会反序列化失败,清 session 或写迁移脚本。
  8. ext-redis 版本:5.x 以下不支持 PHP 8,表现为构造函数抛 RedisException,不是连接失败,排查方向别搞错。
  9. 本地/线上版本不一致:8.2 本机跑过 composer install 后,lock 文件锁入 8.2 专属语法(如枚举),线上 8.1 直接白屏。本机必须用目标 PHP 版本重新 install。

适用边界

逐级升级不是唯一答案:小代码库高测试覆盖可以考虑重写;几十万行不能停业务的必须 Strangler;其余场景逐级升级的性价比最高。判断标准是代码量、测试覆盖度、业务连续性,三个维度。

最后补一个环境判断:页面间歇 500 且无日志,先查 error_log 是不是还指向 5.6 的旧路径,8.x 下 www-data 没权限会静默吞日志。这种问题经常被当成代码 bug 查半天。

复制全文 生成海报 PHP 升级 迁移 兼容

推荐文章

程序员茄子在线接单