最近把项目从PHP 7.0升级到了PHP 7.2还提前体验了PHP 7.3的RC版本(PHP 7.3预计年底正式发布)。

本来以为升级PHP版本是一件很简单的事情改改配置测测功能就完事了。但是没想到升级的过程中踩了很多坑有些坑让我熬夜到很晚才解决。

今天想记录一下升级PHP 7.2/7.3过程中遇到的各种坑和解决方法。希望这些经验能帮大家在升级PHP的时候,少踩坑少熬夜。

一、为什么要升级

先说说为什么要升级PHP版本。

PHP 7.0已经发布很久了官方已经停止维护了不再有安全更新和bug修复。继续用PHP 7.0有安全风险。

而且PHP 7.2和即将发布的PHP 7.3在性能上有进一步提升比PHP 7.0快了不少。还有很多新特性和改进能提升开发效率和代码质量。

另外很多新的框架和库已经不再支持PHP 7.0了要求PHP 7.1甚至PHP 7.2以上。不升级就用不了新的技术。

所以升级PHP版本是必须的只是时间问题。

二、升级前的准备

升级之前,一定要做好准备工作,不然很容易出问题。

我最开始就是准备不足直接在测试环境换了PHP版本结果一跑项目满屏的报错懵了。

后来总结了升级前需要做的准备工作:

1. 了解新版本的变化

升级之前,一定要仔细看官方的迁移指南了解新版本有哪些变化哪些函数被废弃了哪些语法变了哪些配置改了。

PHP 7.2的主要变化:

  • 废弃了create_function函数
  • 废弃了each函数
  • 废弃了parse_str的第二个参数不传的用法
  • 废弃了__autoload函数
  • 类型提示更严格
  • 对象不能当数组用了
  • 新增了object类型
  • 密码哈希算法改进
  • 等等

PHP 7.3的主要变化(RC版本):

  • Heredoc/Nowdoc语法更灵活
  • 数组解构支持引用
  • JSON错误处理改进
  • 新增is_countable函数
  • 新增arraykeyfirst和arraykeylast函数
  • 性能进一步提升
  • 等等

2. 检查代码中的废弃函数

升级之前,用工具扫描一下代码看看有没有用被废弃的函数和语法。

可以用PHP_CodeSniffer或者PHPCompatibility等工具扫描代码检查兼容性。

我就是用PHPCompatibility扫描了代码发现了很多被废弃的函数提前改了省了很多事。

3. 做好测试

升级之前,确保项目有足够的测试用例单元测试集成测试功能测试都要有。

升级之后,跑一遍测试能快速发现问题。

如果没有测试升级就很危险很容易有漏网之鱼线上出问题。

4. 备份

升级之前,一定要备份代码和数据万一升级失败能快速回滚。

三、踩过的坑

下面详细说说升级过程中踩过的那些坑。

坑1:each函数被废弃

第一个坑就是each函数被废弃了。

PHP 7.2废弃了each函数。我们的项目里有很多地方用了each函数特别是一些老的代码和第三方库。

each函数的作用是返回数组中当前元素的键和值并将指针前移。以前经常和list配合遍历数组。

比如:

while (list($key, $value) = each($arr)) {
    // do something
}

PHP 7.2之后each函数被废弃了会报E_DEPRECATED警告。而且PHP 8.0之后,会被移除。

解决方法是用foreach代替each。上面的代码可以改成:

foreach ($arr as $key => $value) {
    // do something
}

foreach更简洁更高效也更易读。

我把项目里所有用each的地方都改成了foreach花了不少时间。特别是一些第三方库也用了each需要升级库的版本,或者自己改。

坑2:create_function被废弃

第二个坑是create_function被废弃了。

create_function函数用来创建匿名函数,但是它的实现很不安全,因为它是用eval实现的容易有代码注入的风险。

PHP 7.2废弃了create_function。

我们的项目里有一些老的代码用了create_function。比如:

$func = create_function('$a, $b', 'return $a + $b;');

解决方法是用匿名函数(闭包)代替。上面的代码可以改成:

$func = function($a, $b) {
    return $a + $b;
};

匿名函数更安全更高效也更易读。

坑3:对象不能当数组用了

第三个坑是对象不能当数组用了。

PHP 7.2之前,有些对象实现了ArrayAccess接口可以像数组一样访问。但是有些没有实现ArrayAccess的对象也能勉强当数组用不会报致命错误只是警告。

PHP 7.2之后,对象,如果没有实现ArrayAccess接口就不能当数组用了会报致命错误。

我们的项目里有一些地方把对象当数组用,比如:

$obj = new stdClass();
$obj->name = "test";
echo $obj['name']; // 错误

这种代码在PHP 7.2之前,可能只是警告,但是在PHP 7.2之后,会报致命错误。

解决方法是要么把对象改成数组要么给对象实现ArrayAccess接口要么用对象属性的方式访问。

我把项目里这种代码都找出来改了花了不少时间。

坑4:类型提示更严格

第四个坑是类型提示更严格了。

PHP 7.2对类型提示的检查更严格了。比如函数参数声明了类型传入不匹配的类型会报TypeError致命错误而不是之前,的警告。

而且PHP 7.2新增了object类型可以声明参数和,返回值是object类型。

我们的项目里有一些函数声明了类型提示,但是调用的时候,传入了不匹配的类型之前,只是警告不影响运行,但是PHP 7.2之后,直接报致命错误。

比如:

function test(int $num) {
    echo $num;
}

test("123"); // PHP 7.2之前,警告自动转换;PHP 7.2之后,致命错误

解决方法是确保传入的参数类型正确,或者在函数内部做类型转换。

我把项目里这种类型不匹配的调用都找出来改了花了很多时间。

坑5:count函数对非可数对象报错

第五个坑是count函数对非可数对象报错。

PHP 7.2之前count函数对任何变量都能用,即使不是数组,或者可数对象也只是返回1或者0不会报错。

PHP 7.2之后count函数对非可数的变量会报E_WARNING警告。

比如:

$var = null;
echo count($var); // PHP 7.2之后,警告

我们的项目里有很多地方用count判断变量是否为空,但是变量可能不是数组导致警告。

解决方法是在count之前,先判断变量是否是数组,或者可数对象。PHP 7.3新增了is_countable函数可以用来判断。

PHP 7.2可以用is_array和instanceof Countable判断:

if (is_array($var) || $var instanceof Countable) {
    echo count($var);
}

我把项目里这种代码都改了花了很多时间。

坑6:parse_str的用法变化

第六个坑是parse_str函数的用法变化。

PHP 7.2废弃了parsestr函数不传第二个参数的用法。如果不传第二个参数parsestr会把变量注册到当前作用域这很不安全容易导致变量覆盖。

PHP 7.2之后,不传第二个参数会报E_DEPRECATED警告。PHP 8.0之后,会强制要求第二个参数。

我们的项目里有一些地方用了parse_str没传第二个参数。

解决方法是加上第二个参数把结果存到数组里:

parse_str($str, $output);
echo $output['name'];

坑7:__autoload被废弃

第七个坑是__autoload函数被废弃了。

PHP 7.2废弃了__autoload函数推荐用splautoloadregister代替。

我们的项目里有一些老的代码用了__autoload。

解决方法是改成splautoloadregister:

// 以前
function __autoload($class) {
    include $class . '.php';
}

// 现在
spl_autoload_register(function($class) {
    include $class . '.php';
});

坑8:扩展兼容性问题

第八个坑是扩展兼容性问题。

升级PHP版本之后,有些扩展可能不兼容需要升级扩展的版本,或者重新编译。

我们的项目用了很多扩展,比如redismemcachedmongodbimagick等等。升级PHP 7.2之后,有些扩展的旧版本不兼容需要升级。

特别是一些不常用的扩展可能还没有支持PHP 7.2的版本需要等,或者自己编译,或者找替代方案。

我升级的时候,就遇到了一个扩展不支持PHP 7.2折腾了很久最后找了替代方案才解决。

所以升级之前,一定要检查所有用的扩展是否支持新的PHP版本。

坑9:性能问题

第九个坑是性能问题。

本来以为升级PHP 7.2性能会提升,但是没想到升级之后,有些地方性能反而下降了。

后来排查发现是,因为PHP 7.2对一些操作的处理变了导致某些代码性能下降。

比如PHP 7.2对数组的处理有一些变化某些数组操作比之前,慢了。

还有OPcache的配置也需要调整升级之后,原来的OPcache配置可能不是最优的。

解决方法是升级之后,做性能测试找出性能瓶颈针对性优化。调整OPcache配置优化慢的代码。

我花了几天时间做性能优化才让性能比升级前有提升。

坑10:PHP 7.3的新坑

提前体验PHP 7.3RC版本的时候,也遇到了一些新的坑。

比如PHP 7.3对Heredoc/Nowdoc的语法更灵活了闭合标记可以缩进也可以不换行。但是这导致一些老的Heredoc代码行为变了需要注意。

比如PHP 7.3新增了is_countable函数,但是PHP 7.2没有这个函数,如果代码里用了就不兼容PHP 7.2。

还有PHP 7.3对JSON错误处理改进了新增了JSONTHROWONERROR选项,但是老的代码可能还是用jsonlast_error判断错误需要注意。

因为PHP 7.3还没正式发布RC版本可能,还有一些bug和不稳定的地方。所以不建议在生产环境用RC版本等正式版发布再升级。

四、升级的经验总结

踩了这么多坑总结了一些升级的经验。

1. 不要急慢慢来

升级PHP版本不要急不要想着一天就升级完。要慢慢来一步一步先在测试环境升级充分测试没问题了再上生产环境。

2. 充分测试

升级之前,和升级之后,都要充分测试。单元测试集成测试功能测试性能测试都要跑。确保没有问题了再上线。

3. 用工具扫描代码

升级之前,用PHPCompatibility等工具扫描代码找出不兼容的地方提前改。

4. 关注废弃的函数和语法

仔细看官方的迁移指南了解哪些函数和语法被废弃了提前改。

5. 检查扩展兼容性

升级之前,检查所有用的扩展是否支持新的PHP版本。

6. 做好回滚准备

升级之前,做好备份和回滚准备万一升级失败能快速回滚。

7. 小步迭代

如果项目很大可以小步迭代先升级一部分测试没问题再升级另一部分。不要一下子全部升级出了问题不好排查。

五、写在最后

以上就是我升级PHP 7.2/7.3过程中踩过的坑和总结的经验。

升级PHP版本确实不是一件简单的事情需要耐心和细心。但是升级之后,性能提升安全性提升新特性也很有用这些付出都是值得的。

希望我的这些经验能帮大家在升级PHP的时候,少踩坑少熬夜。

如果大家在升级PHP的过程中也踩过什么坑欢迎在评论区留言我们一起交流。

最后用一句话结束这篇文章:"升级有风险操作需谨慎,但是不升级风险更大。"

愿大家都能顺利升级PHP版本享受新版本带来的好处。