一次 PHP 8 升级实录:从 5.6 到 8.3,我踩过的那些坑
背景
公司有一套核心业务系统,2016 年开始写的,跑在 PHP 5.6 上。
2024 年初,因为 5.6 早就不维护了,安全扫描天天告警,加上新来的开发都用 8.x 的语法,老代码他们看不懂,于是老板拍板:升级。
我作为技术负责人,带着两个人,用了 47 天完成升级。下面是真实的踩坑记录。
一、致命级错误(直接白屏)
1. mysql_* 系列函数全删
老代码里还有这种东西:
$conn = mysql_connect('localhost', 'root', '');
mysql_select_db('demo', $conn);
$result = mysql_query("SELECT * FROM users");
PHP 7 起就移除了,必须改成 mysqli_* 或 PDO。我们系统里有一百多处,写了个脚本批量改。
2. create_function() 被移除
$fn = create_function('$a,$b', 'return $a+$b;');
改成箭头函数或匿名函数:
$fn = fn($a, $b) => $a + $b;
3. __toString 必须返回 string
PHP 8 起,__toString() 里如果 return null 或 return 0; 会抛 TypeError。我们有个老的 Money 类踩了雷:
public function __toString() {
return $this->amount; // amount 是 float,炸
}
改成:
public function __toString(): string {
return (string) $this->amount;
}
二、行为变化(不报错但结果不对)
1. 字符串数字比较
PHP 8 之前:
var_dump("0" == "0e12345"); // bool(true)
var_dump(0 == "abc"); // bool(true)
PHP 8 之后:
var_dump("0" == "0e12345"); // bool(false)
var_dump(0 == "abc"); // bool(false)
我们有个权限校验逻辑,老版本下"幸运地"通过了,升级后管理员突然只能看到普通用户权限。这个 bug 上线 4 小时才被发现。
2. match 是新关键字
老代码里有:
public function match($a, $b) { ... }
PHP 8 把 match 升级为保留字,方法名不能叫这个。批量改名为 matchRule。
3. 错误报告级别变化
PHP 8 默认 error_reporting = E_ALL,以前被忽略的 undefined variable 现在全抛出来。我们日志一天涨了 20G。
三、隐性性能问题
1. strpos 类型严格化
if (strpos($haystack, $needle) === false) { ... }
如果 $needle 是 null,PHP 8 会报警告。排查了好几处。
2. JIT 在我们的场景反而变慢
开了 JIT 后,接口平均响应时间从 120ms 涨到了 180ms。
排查后发现:我们业务是 IO 密集型(大量 MySQL 调用 + 第三方 HTTP),JIT 对 CPU 密集型才有提升。关掉 JIT,保留 opcache,反而更快。
四、最终收益
升级完成后:
| 指标 | 升级前 (5.6) | 升级后 (8.3) |
|---|---|---|
| 平均响应时间 | 220ms | 130ms |
| 内存占用 | 1.2GB | 600MB |
| 单机 QPS | 80 | 180 |
| Composer 包兼容性 | 60% | 100% |
最大的收益其实不是性能,是新人愿意来了——他们不用再忍受 mysql_query 的祖传代码。
五、建议
如果你也在犹豫要不要升级:
- 必须升。5.6 / 7.0 已经是上个时代了。
-
用
php-cs-fixer+ 自定义规则先扫一遍,能提前发现 80% 的问题。 - 测试用例比代码本身重要。我们有 1200 个集成测试,是升级能成功的最大保障。
- 灰度发布。我们用了 2 周时间,从 5% 流量逐步切到 100%。
升级这事,技术上不难,难的是胆子和耐心。