穿越说明:本文写于2025年3月,PHP 8.4已正式发布,PHP 8.5尚未正式发布。文中关于PHP 8.5的特性基于官方路线图和社区预期,实际发布后可能有所不同。

最近,我们团队完成了一次PHP版本的大迁移,从PHP 7.4直接升级到了8.4,并且为8.5做了准备。

这个系统是一个运行了多年的老项目,代码量几十万行,用了很多旧的写法和废弃的特性。迁移的过程比想象中复杂,踩了不少坑,但最终还是顺利完成了,而且性能提升明显。

这篇文章,我想分享一下这次迁移的实战经历,从迁移前的准备、兼容性检查、代码修改、测试验证到性能优化,聊聊PHP版本迁移的完整流程和踩坑经验。希望能给正在做或者打算做PHP版本迁移的朋友一些参考。

为什么要迁移

先说说为什么要花这么大精力做版本迁移。

第一个原因是,PHP 7.4已经停止维护了。PHP 7.4的安全支持在2022年底就结束了,不再有安全更新。继续用7.4,意味着有安全漏洞也不会被修复,风险很大。对于线上系统来说,这是不可接受的。

第二个原因是,性能提升。PHP 8.x的性能比7.4有明显提升,尤其是JIT(即时编译)的引入,在计算密集型场景下提升很大。我们迁移之后,整体性能提升了20%-30%,响应时间更短,服务器负载更低。

第三个原因是,新特性。PHP 8.x引入了很多有用的新特性,比如命名参数、联合类型、匹配表达式、构造器属性提升、枚举、只读属性、Fibers等。这些特性能让代码更简洁、更安全、更易维护。迁移到新版本,才能用上这些特性。

第四个原因是,生态要求。越来越多的PHP库和框架,开始要求PHP 8.x的版本。比如Laravel 11要求PHP 8.2以上,Symfony 7要求PHP 8.2以上。如果不升级PHP版本,很多新的库和框架都用不了,技术栈会越来越落后。

综合这些原因,我们决定做一次彻底的版本迁移,直接升到8.4,并为8.5做好准备。

迁移前的准备

迁移之前,我们做了充分的准备工作。

第一步是,评估现状。我们先梳理了系统的情况:代码量多大、用了哪些框架和库、有哪些废弃的特性、测试覆盖率怎么样、有没有依赖特定PHP版本的扩展。评估之后,我们对迁移的工作量和风险有了大致的了解。

第二步是,制定计划。我们把迁移分成了几个阶段:兼容性检查、代码修改、测试验证、预发布、全量上线。每个阶段都有明确的目标和时间节点。我们还制定了回滚方案,如果迁移后出问题,可以快速回滚到旧版本。

第三步是,搭建环境。我们搭建了一套PHP 8.4的开发和测试环境,和生产环境隔离。开发人员可以在新环境中开发和测试,不影响线上系统。我们还用Docker做了环境标准化,确保开发、测试、生产环境的PHP版本一致。

第四步是,学习新特性。我们组织了团队学习PHP 8.x的新特性和废弃的特性,让每个人都了解变化。尤其是废弃的特性,必须知道哪些写法不能用了,需要改成什么。

第五步是,备份。迁移前做了完整的代码和数据备份,确保出问题可以回滚。

兼容性检查

准备工作做好之后,开始做兼容性检查。这是迁移过程中非常重要的一步,能帮你发现大部分问题。

我们用了几个工具来做兼容性检查:

第一个工具是,PHP_CodeSniffer(phpcs)。用PHPCompatibility标准,可以扫描代码中不兼容新版本的写法。比如废弃的函数、不兼容的语法、移除的扩展等。我们跑了一下,发现了几百个问题,主要集中在废弃的函数和类型相关的问题上。

第二个工具是,PHPStan。这是一个静态分析工具,可以发现类型相关的问题、未定义的变量、不匹配的参数等。PHP 8.x对类型的要求更严格,很多在7.4中能跑的代码,在8.x中会报错。PHPStan能帮我们提前发现这些问题。

第三个工具是,PHPUnit。我们有一定的单元测试覆盖率,把测试环境切换到PHP 8.4,跑一遍测试,看看哪些测试失败了。测试失败的地方,通常就是兼容性问题。

第四个工具是,手动代码审查。工具能发现大部分问题,但有些问题(比如逻辑变化、隐式类型转换的差异)工具发现不了,需要人工审查。我们重点审查了核心业务逻辑和复杂的代码。

通过这些检查,我们整理出了一个问题清单,按严重程度分类:

  • 严重问题:会导致代码报错或者行为变化的,必须修改。
  • 中等问题:废弃的特性,虽然还能运行但有警告,建议修改。
  • 轻微问题:代码风格或者优化建议,可以后续修改。

主要的兼容性问题

我们遇到的主要兼容性问题有以下几类。

第一类是,废弃的函数和特性。PHP 8.x移除和废弃了很多旧的函数和特性,比如:

  • each()函数在8.0中被移除,需要用foreach或者key()/current()替代。
  • create_function()在8.0中被移除,需要用匿名函数替代。
  • 字符串的花括号访问($str{0})在8.0中被移除,需要用方括号($str[0])。
  • 可选参数在必填参数之前的写法,在8.0中被废弃,需要调整参数顺序。
  • 隐式的null到非null类型的转换,在8.1中被废弃,需要显式处理。
  • 自动全局变量(register_globals)早就移除了,但有些老代码还在用。

这些问题,大部分可以用工具自动修复(比如Rector),但有些需要手动修改。

第二类是,类型系统的变化。PHP 8.x的类型系统更严格了,很多隐式的类型转换不再被允许。比如:

  • 函数参数如果声明了类型,传错类型会报TypeError,而不是警告。
  • 返回类型不匹配会报错。
  • 算术运算中的类型处理有变化,比如null + 1在8.x中会有警告。
  • 字符串和数字的比较规则有变化,非数字字符串和数字比较时,8.x的行为和7.4不一样。

这些问题,需要仔细审查代码,确保类型使用正确。我们的做法是,逐步给代码加上类型声明,既解决了兼容性问题,又提升了代码质量。

第三类是,扩展的变化。有些PHP扩展在8.x中被移除了或者不再维护,比如:

  • mysql扩展早就移除了,需要用mysqli或者PDO。
  • 有些老的扩展可能没有8.x的版本,需要找替代方案。
  • 扩展的API有变化,自定义扩展需要重新编译。

我们的系统用了几个自定义扩展,迁移的时候需要重新编译,并且修改了一些不兼容的API调用。

第四类是,错误处理的变化。PHP 8.x中,很多错误从警告(Warning)变成了异常(Exception),或者从通知(Notice)变成了警告。比如:

  • 未定义的变量,从Notice变成了Warning。
  • 数组越界访问,从Notice变成了Warning。
  • 除以零,从Warning变成了DivisionByZeroError异常。

这些变化,可能会导致之前被忽略的错误现在暴露出来,甚至中断程序执行。需要检查代码中的错误处理,确保这些变化不会导致业务异常。

代码修改

兼容性检查完成之后,开始修改代码。

我们的修改策略是:先改严重问题,确保代码能在PHP 8.4下运行;再改中等问题,消除废弃警告;最后做代码优化,用上新特性。

修改的方法有几种:

第一种是,用Rector自动重构。Rector是一个PHP代码重构工具,可以自动把旧版本的代码改成新版本的写法。比如,把each()改成foreach,把create_function()改成匿名函数,给构造函数加属性提升等。我们用Rector处理了大部分机械性的修改,节省了大量时间。

第二种是,手动修改。对于Rector处理不了的问题(比如业务逻辑变化、类型调整),需要手动修改。我们按模块分配任务,每个开发人员负责几个模块,修改后提交代码审查。

第三种是,逐步加类型。PHP 8.x的类型系统很强大,我们利用这次迁移,给核心代码加上了类型声明。包括参数类型、返回类型、属性类型。加类型不仅解决了兼容性问题,还让代码更安全、更易维护,IDE的提示也更准确。

第四种是,用上新特性。在兼容性问题解决之后,我们开始用PHP 8.x的新特性优化代码,比如:

  • 用构造器属性提升,简化类的构造函数。
  • 用match表达式替代复杂的switch。
  • 用枚举(Enum)替代常量集合。
  • 用只读属性(readonly)替代只在构造函数中赋值的属性。
  • 用命名参数,提高函数调用的可读性。
  • 用联合类型,处理参数可以是多种类型的情况。
  • 用Fibers,处理异步操作。

这些新特性的使用,让代码更简洁、更现代。但我们也注意不过度使用,在团队成员都熟悉的前提下逐步引入。

测试验证

代码修改完成之后,进入测试验证阶段。这是确保迁移质量的关键环节。

我们做了几轮测试:

第一轮是,单元测试。把测试环境切换到PHP 8.4,跑所有的单元测试。我们的测试覆盖率大概60%,核心模块的覆盖率更高。单元测试帮我们发现了很多逻辑问题,尤其是类型变化导致的问题。

第二轮是,集成测试。测试各个模块之间的协作,确保接口调用、数据流转正常。我们用了自动化的集成测试框架,模拟真实的业务场景。

第三轮是,性能测试。对比PHP 7.4和8.4的性能,确保迁移后性能没有下降,最好是有提升。我们用了压力测试工具(比如ab、wrk),模拟高并发场景,测试响应时间和吞吐量。测试结果显示,8.4的性能比7.4提升了20%左右,符合预期。

第四轮是,用户验收测试。让产品和测试团队在测试环境中,按照真实的用户场景做全面的测试。重点测试核心业务流程,确保没有功能异常。

第五轮是,灰度测试。我们先把一小部分流量切到PHP 8.4的环境,观察一段时间。监控错误率、响应时间、用户反馈。如果没有问题,再逐步扩大流量比例,最后全量切换。

测试过程中,我们发现了一些问题,比如:

  • 某些第三方库不兼容PHP 8.4,需要升级版本或者找替代方案。
  • 某些隐式类型转换的行为变化,导致业务逻辑有细微差异。
  • 某些扩展在8.4下有bug,需要升级或者打补丁。

这些问题都在上线前解决了,确保了迁移的质量。

上线和回滚

测试通过之后,开始上线。

我们的上线策略是灰度发布:

  1. 先在测试环境验证一周,确保稳定。
  2. 然后把5%的流量切到PHP 8.4,观察24小时。
  3. 没有问题的话,扩大到20%,再观察24小时。
  4. 然后扩大到50%,观察24小时。
  5. 最后全量切换到PHP 8.4。

整个灰度过程持续了一周,期间密切监控各项指标。如果出现问题,可以随时切回7.4。

上线后,我们做了这些监控:

  • 错误率:确保没有异常升高。
  • 响应时间:确保性能符合预期。
  • 服务器资源:CPU、内存、IO的使用情况。
  • 业务指标:核心业务的转化率、成功率等。
  • 用户反馈:收集用户的问题和建议。

上线后一周,各项指标都正常,甚至比之前更好。我们正式关闭了PHP 7.4的环境,迁移完成。

同时,我们也为PHP 8.5做了准备。8.5预计会在2025年底发布,我们关注了8.5的新特性和废弃计划,确保代码中没有使用8.5中会被移除的特性。这样等8.5发布后,可以快速升级。

性能优化

迁移到PHP 8.4之后,我们还做了一些性能优化,充分利用新版本的能力。

第一个优化是,开启JIT。PHP 8.0引入了JIT编译器,在计算密集型场景下能显著提升性能。我们的系统有一些图像处理和数据计算的功能,开启JIT之后,这些功能的性能提升了30%左右。JIT的配置很简单,在php.ini中设置opcache.jitbuffersize和opcache.jit即可。

第二个优化是,优化OPcache。OPcache对PHP性能影响很大,我们调整了OPcache的配置,比如增大内存、开启快速关闭、优化验证频率等。迁移到8.4后,OPcache的效率也有提升。

第三个优化是,预加载(Preloading)。PHP 7.4引入了预加载功能,可以在服务器启动时把常用的类加载到内存中,避免每次请求都加载。我们把核心框架和常用类做了预加载,响应时间又降了一些。

第四个优化是,代码层面的优化。利用PHP 8.x的新特性,优化了一些性能瓶颈。比如,用更高效的数组操作、减少不必要的类型转换、用Fibers优化IO密集型操作等。

通过这些优化,我们的系统整体性能比迁移前提升了30%以上,服务器的负载降低了,用户体验也更好了。

踩过的坑

分享几个迁移过程中踩过的坑。

第一个坑是,第三方库不兼容。有些老的第三方库,很久没更新了,不支持PHP 8.x。我们有一个支付SDK,用了一个废弃的PHP特性,在8.4下直接报错。联系厂商,厂商说不再维护了。最后我们要么自己fork一个版本修改,要么换一个SDK,花了不少时间。建议迁移前先检查所有依赖的兼容性。

第二个坑是,隐式类型转换的变化。PHP 8.x对类型的处理更严格了,有些在7.4中"正常工作"的代码,在8.x中行为变了。比如,我们有一段代码,把字符串和数字比较,7.4中是按数字比较,8.x中非数字字符串是按字符串比较,导致逻辑错误。这种问题工具很难发现,需要仔细测试。

第三个坑是,扩展的兼容性。我们用了几个自定义扩展,迁移到8.4需要重新编译。其中一个扩展用了旧的Zend API,在8.4下编译失败,需要修改扩展的源码。花了不少时间研究扩展的API变化。如果有自定义扩展,迁移前要做好评估。

第四个坑是,测试覆盖率不足。我们的测试覆盖率只有60%,有些边缘场景没有覆盖到。灰度测试的时候,发现了一个边缘场景的bug,就是因为没有测试覆盖。建议迁移前尽量提高测试覆盖率,尤其是核心业务逻辑。

第五个坑是,急于求成。一开始我们想一次性把所有问题都改完,结果改了很多代码,测试的时候出了很多问题,很难定位。后来我们调整了策略,分阶段修改,先保证能运行,再逐步优化,每一步都测试,进度反而更快了。

迁移建议

最后,给打算做PHP版本迁移的朋友几个建议。

第一,不要跳过版本。如果你的系统还在用PHP 5.x或者7.0-7.3,建议先升到7.4,再升到8.x。直接跨多个大版本升级,兼容性问题会更多,风险更大。

第二,充分利用工具。PHP_CodeSniffer、PHPStan、Rector、PHPUnit这些工具,能大大提高迁移的效率和质量。不要纯手动修改,工具能处理大部分机械性的工作。

第三,先做兼容性检查,再改代码。不要上来就改代码,先全面检查,了解问题的范围和严重程度,制定计划,再有步骤地修改。

第四,测试是关键。迁移的风险主要在于未发现的兼容性问题。充分的测试(单元测试、集成测试、灰度测试)是降低风险的最好方法。

第五,做好回滚方案。迁移有风险,一定要有回滚方案。灰度发布、流量切换、完整备份,这些都要准备好。出了问题能快速回滚,就不会造成大的影响。

第六,利用迁移做技术升级。版本迁移不只是改兼容性问题,也是一个技术升级的好机会。可以趁机重构烂代码、加类型声明、用新特性、优化性能。一次迁移,多重收益。

第七,关注PHP 8.5的动态。PHP的版本更新很快,8.5很快就会来。迁移到8.4之后,可以关注8.5的新特性和废弃计划,提前做好准备,这样下次升级就轻松了。

写在最后

PHP版本迁移是一个耗时耗力的工作,但也是值得的。

迁移到PHP 8.4之后,我们的系统更安全、更快、更易维护。而且,通过这次迁移,我们还做了代码重构和性能优化,技术栈也更新了。从长远来看,这些投入都是值得的。

PHP还在持续发展,8.5、9.0都会陆续到来。作为PHP开发者,要跟上版本的步伐,不要让系统停留在过时的版本上。定期升级PHP版本,是保持系统健康和技术竞争力的重要手段。

希望我们的迁移经验,能给正在做或者打算做PHP版本迁移的朋友一些参考。如果你也有PHP迁移的经验或者问题,欢迎在评论区交流。