一次 PHP 8 升级实录:从 5.6 到 8.3,我踩过的那些坑

📅 2026-05-13 · 👁 6 次阅读

背景

公司有一套核心业务系统,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 nullreturn 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) { ... }

如果 $needlenull,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 的祖传代码。

五、建议

如果你也在犹豫要不要升级:

  1. 必须升。5.6 / 7.0 已经是上个时代了。
  2. php-cs-fixer + 自定义规则先扫一遍,能提前发现 80% 的问题。
  3. 测试用例比代码本身重要。我们有 1200 个集成测试,是升级能成功的最大保障。
  4. 灰度发布。我们用了 2 周时间,从 5% 流量逐步切到 100%。

升级这事,技术上不难,难的是胆子和耐心

推荐阅读

← 返回首页