2015年12月,PHP 7正式发布。

当时整个PHP社区都沸腾了——PHP 7号称性能比PHP 5.6提升一倍,甚至能和HHVM媲美。对于我们这些PHPer来说,这简直是天大的好消息。不用改代码,性能就能翻倍,谁不心动?

但是心动归心动,真正要把线上项目从PHP 5.6升级到PHP 7,还是需要勇气的。毕竟线上项目稳定运行了很久,谁也不想因为升级搞出线上故障。

2016年中,我终于下定决心,把公司的一个中型项目从PHP 5.6升级到了PHP 7。整个过程花了大约两周,性能确实提升了很多,但是也踩了不少坑。

今天就来聊聊,我的PHP 7升级经历,以及那些你可能会踩到的坑。

一、为什么要升级PHP 7

在聊升级过程之前,先说说为什么要升级PHP 7。

1. 性能翻倍

这是PHP 7最大的卖点。PHP 7使用了全新的Zend Engine 3,对内存管理和数据结构做了大量优化。官方 benchmarks 显示,PHP 7的性能比PHP 5.6提升了约一倍,有些场景甚至提升了2-3倍。

实际项目中,我们的API接口响应时间平均下降了40%-50%,服务器CPU使用率下降了约30%。这意味着,同样的服务器,能承载更多的流量,节省了服务器成本。

2. 更低的内存占用

PHP 7对数据结构做了优化,同样的数据,内存占用比PHP 5.6少了约30%-50%。对于大数组、大对象的场景,内存节省更加明显。

3. 新的语言特性

PHP 7引入了很多新的语言特性,让代码更现代、更安全:

  • 标量类型声明(int、string、bool、float)
  • 返回值类型声明
  • 匿名类
  • null合并运算符(??)
  • 太空船运算符(<=>)
  • 常量数组
  • 更多的Error可捕获

4. 长期支持

PHP 5.6在2018年底就停止支持了,而PHP 7有长期支持版本(PHP 7.0支持到2018年底,PHP 7.1支持到2019年底,PHP 7.2支持到2020年底)。升级到PHP 7,能获得更长时间的安全更新和bug修复。

5. 生态趋势

越来越多的PHP框架和库开始要求PHP 7+,比如Laravel 5.5+、Symfony 4+、PHPUnit 6+等。不升级PHP 7,就无法使用这些新版本的框架和库。

二、PHP 7的新特性

升级之前,先了解一下PHP 7的新特性,这样升级后才能用上这些新特性。

1. 标量类型声明

PHP 7支持标量类型声明(int、string、bool、float):

function sum(int $a, int $b): int {
    return $a + $b;
}

sum(1, 2);      // 3
sum('1', '2');  // 在严格模式下会报错,非严格模式下会转换为1+2=3

默认是弱类型(会自动转换),如果要严格类型,需要在文件开头加declare(strict_types=1);

2. 返回值类型声明

function getUser(): array {
    return ['id' => 1, 'name' => '张三'];
}

如果返回值类型不匹配,会报错。

3. null合并运算符(??)

// PHP 5
$name = isset($_GET['name']) ? $_GET['name'] : '默认';

// PHP 7
$name = $_GET['name'] ?? '默认';

// 还可以链式
$name = $_GET['name'] ?? $_POST['name'] ?? '默认';

这个运算符非常实用,能让代码简洁很多。

4. 太空船运算符(<=>)

// 比较两个值,返回-1、0或1
echo 1 <=> 1;  // 0
echo 1 <=> 2;  // -1
echo 2 <=> 1;  // 1

// 用于排序
usort($array, function($a, $b) {
    return $a['price'] <=> $b['price'];
});

5. 匿名类

$logger = new class {
    public function log($msg) {
        echo $msg;
    }
};

$logger->log('hello');

匿名类适合简单的一次性对象,不需要单独定义一个类。

6. 常量数组

define('CONFIG', [
    'host' => 'localhost',
    'port' => 3306,
]);

echo CONFIG['host'];  // localhost

PHP 5.6只支持const定义数组常量,PHP 7支持define定义数组常量。

7. 更多的Error可捕获

PHP 7中,很多以前的Fatal Error变成了可捕获的Error:

try {
    $result = 1 / 0;  // DivisionByZeroError
} catch (DivisionByZeroError $e) {
    echo '错误:' . $e->getMessage();
}

try {
    $obj = null;
    $obj->method();  // Error: Call to a member function method() on null
} catch (Error $e) {
    echo '错误:' . $e->getMessage();
}

这让错误处理更加灵活。

三、升级前的准备

升级PHP 7之前,需要做一些准备工作,避免升级后出问题。

1. 检查PHP版本要求

首先,确认你的项目依赖的框架、库、扩展是否支持PHP 7。大部分主流框架和库都已经支持PHP 7,但是一些老旧的、不再维护的库可能不支持。

如果有不支持PHP 7的依赖,需要先升级或替换这些依赖。

2. 代码兼容性检查

使用PHP 7的兼容性检查工具,扫描代码中可能不兼容的地方:

  • php7mar(PHP 7 Migration Assistant Report):官方推荐的迁移工具,能扫描代码中不兼容的地方
  • PHP_CodeSniffer:配合PHP 7的标准,能检查代码兼容性
  • Phan/PHPStan:静态分析工具,能发现类型相关的问题

我们用php7mar扫描了整个项目,发现了几十处可能不兼容的地方,大部分是小问题,但是有几处是必须修改的。

3. 测试环境升级

先在测试环境升级到PHP 7,跑一遍所有测试用例,确保功能正常。不要直接在生产环境升级,那样风险太大。

我们在测试环境升级后,跑了单元测试、集成测试、功能测试,发现了一些问题,修复后才在生产环境升级。

4. 备份

升级前,备份代码、数据库、配置文件,以防升级出问题可以回滚。

四、升级过程中踩的坑

接下来是重点——升级过程中踩的坑。

坑1:mysql扩展被移除

PHP 7移除了老旧的mysql扩展(mysql*函数),只保留了mysqli和PDOMySQL。

我们的项目中有一些老代码还在用mysqlquery()mysqlconnect()等函数,升级到PHP 7后直接报错。

解决方案

  • 把所有mysql函数替换为mysqli或PDO
  • 如果代码量大,可以先写一个兼容层(用mysqli封装mysql_*函数),临时过渡,然后逐步替换

我们项目中mysql扩展的使用不算太多,花了一天时间全部替换为mysqli。

坑2:ereg扩展被移除

PHP 7移除了ereg扩展(ereg_*函数),这些函数已经被废弃很久了,PHP 5.3就开始Deprecated了。

我们项目中有几处用了ereg()做正则匹配,升级后报错。

解决方案

  • ereg()替换为preg_match()
  • 注意正则表达式的语法差异:ereg用的是POSIX正则,preg用的是PCRE正则,需要转换
// 旧代码
if (ereg('^[0-9]+$', $str)) { ... }

// 新代码
if (preg_match('/^[0-9]+$/', $str)) { ... }

坑3:mcrypt扩展被废弃

PHP 7.1开始废弃mcrypt扩展,PHP 7.2移除。虽然PHP 7.0还能用mcrypt,但是已经不推荐了。

我们项目中用mcrypt做加密,升级后虽然能用,但是会有Deprecated警告。

解决方案

  • 用openssl扩展替换mcrypt
  • 或者用paragonie/halite等现代加密库
// 旧代码(mcrypt)
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv);

// 新代码(openssl)
$encrypted = openssl_encrypt($data, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv);

坑4:不兼容的语法变化

PHP 7有一些语法变化,可能导致旧代码报错:

  • foreach的list()语法变化:PHP 7中,foreach ($array as list($a, $b))的行为有变化
  • 十六进制字符串的数值转换:PHP 7不再把十六进制字符串当作数字
  • \u{} Unicode转义:PHP 7新增了Unicode转义语法,如果旧代码中有类似的字符串可能有问题
  • global变量的变化:PHP 7中,global $$variable的行为有变化
  • funcgetarg()等函数的变化:PHP 7中,这些函数返回的是当前值,而不是传入时的值

我们项目中遇到了十六进制字符串的问题——有一处代码用is_numeric('0x123')判断,PHP 5.6返回true,PHP 7返回false,导致逻辑错误。

解决方案

  • 仔细阅读PHP 7的迁移文档,了解所有不兼容的变化
  • 用兼容性检查工具扫描代码
  • 测试环境充分测试

坑5:第三方扩展兼容性

PHP 7需要重新编译所有第三方扩展,有些扩展可能不支持PHP 7。

我们项目中用了几个第三方扩展,其中一个比较冷门的扩展没有PHP 7版本,导致升级受阻。

解决方案

  • 升级前检查所有第三方扩展是否支持PHP 7
  • 不支持的扩展,找替代品,或者自己编译适配
  • 尽量减少对冷门扩展的依赖

我们最后把那个冷门扩展替换成了纯PHP实现的方案,虽然性能稍差,但是能兼容PHP 7。

坑6:opcache配置变化

PHP 7的opcache有一些新的配置项,而且默认配置和PHP 5.6有所不同。

升级后,我们发现opcache的命中率不高,性能没有达到预期。后来调整了opcache配置,性能才提升上来。

推荐的opcache配置

[opcache]
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.max_wasted_percentage=10
opcache.revalidate_freq=60
opcache.validate_timestamps=1
opcache.save_comments=1
opcache.fast_shutdown=1

注意:

  • opcache.memory_consumption:PHP 7建议至少128MB,PHP 5.6默认64MB可能不够
  • opcache.maxacceleratedfiles:根据项目文件数量调整,大项目建议10000以上
  • opcache.revalidate_freq:生产环境可以设大一些(如60秒),减少文件检查开销

坑7:错误处理变化

PHP 7中,很多Fatal Error变成了可捕获的Error。如果你的代码中有seterrorhandler()或者registershutdownfunction()做错误处理,可能需要调整。

我们项目中有一个全局错误处理,用registershutdownfunction()捕获Fatal Error。升级到PHP 7后,有些Error不再触发shutdown function,而是可以被try-catch捕获,导致错误处理逻辑不一致。

解决方案

  • 统一用try-catch捕获Error和Exception
  • 调整seterrorhandler()registershutdownfunction()的逻辑
  • 考虑用Whoops等错误处理库

坑8:大整数处理变化

PHP 7中,64位系统上的整数处理有变化。以前超出PHPINTMAX的整数会自动转为float,PHP 7中在某些场景下行为不同。

我们项目中有一处处理大整数的代码,升级后结果不一致,排查了很久才发现是这个问题。

解决方案

  • 处理大整数时,用字符串或BC Math扩展
  • 不要依赖整数自动转换为float的行为

五、性能测试对比

升级完成后,我们做了性能测试,对比PHP 5.6和PHP 7的性能。

测试环境

  • 服务器:4核8G,SSD
  • 压测工具:ab(Apache Bench)
  • 测试接口:首页列表、文章详情、搜索API

测试结果

接口PHP 5.6 (QPS)PHP 7 (QPS)提升
首页列表320680112%
文章详情450920104%
搜索API18035094%

平均QPS提升了约100%,也就是性能翻倍。

响应时间对比

接口PHP 5.6 (平均响应时间)PHP 7 (平均响应时间)下降
首页列表120ms58ms52%
文章详情85ms42ms51%
搜索API220ms110ms50%

平均响应时间下降了约50%。

内存占用对比

场景PHP 5.6 (内存峰值)PHP 7 (内存峰值)下降
首页列表8.5MB4.2MB51%
文章详情6.8MB3.5MB49%
搜索API12MB6.5MB46%

平均内存占用下降了约50%。

服务器资源对比

  • CPU使用率:PHP 5.6平均65%,PHP 7平均40%,下降约38%
  • 内存使用率:PHP 5.6平均70%,PHP 7平均45%,下降约36%

性能提升确实非常明显,官方宣传的"性能翻倍"不是吹的。

六、升级建议

根据我的升级经验,给准备升级PHP 7的朋友几个建议:

1. 不要急,先做足准备

升级前,充分了解PHP 7的新特性和不兼容变化,用兼容性检查工具扫描代码,在测试环境充分测试。不要直接在生产环境升级。

2. 从小项目开始

如果有多个项目,先从一个小项目开始升级,积累经验,再升级大项目。不要一上来就升级核心项目。

3. 预留足够的时间

升级PHP 7不是改个配置就完事的,可能需要修改代码、替换扩展、调整配置。预留足够的时间,不要赶时间。

我们的中型项目花了约两周时间(一周准备+测试,一周修改+上线),大项目可能需要更久。

4. 做好回滚方案

升级前,做好回滚方案。如果升级后出问题,能快速回滚到PHP 5.6,减少影响。

我们用的是多服务器部署,先升级一台服务器,观察一段时间没问题,再逐步升级其他服务器。如果出问题,把流量切回PHP 5.6的服务器即可。

5. 升级后用上新特性

升级到PHP 7后,不要只是"能跑就行",要逐步用上PHP 7的新特性——标量类型声明、返回值类型声明、null合并运算符等。这些新特性能让代码更现代、更安全、更易维护。

6. 关注后续版本

PHP 7之后还有PHP 7.1、7.2、7.3、7.4等版本,每个版本都有新特性和性能优化。升级到PHP 7后,关注后续版本,适时升级。

七、写在最后

PHP 7升级,性能翻倍,但是也踩了不少坑。从mysql扩展移除、ereg扩展移除、mcrypt废弃,到语法变化、第三方扩展兼容性、opcache配置、错误处理变化,每一个坑都需要认真对待。

但是,这些坑都是值得的。升级到PHP 7后,性能提升明显,服务器资源占用下降,代码也能用上更多现代特性。对于PHP项目来说,升级PHP 7是性价比最高的优化之一。

如果你还在使用PHP 5.6,我强烈建议你尽快升级到PHP 7。PHP 5.6已经停止支持了,继续使用有安全风险。而且,PHP 7的性能提升和新特性,能让你的项目更上一层楼。

最后,用一句话总结:PHP 7升级,痛并快乐着。痛的是踩坑,快乐的是性能翻倍。

愿每一个PHPer,都能顺利升级到PHP 7,享受性能翻倍的快乐。愿PHP这门"世界上最好的语言",在PHP 7之后越来越好。