PHP 8.5 升级排查:哪些行为变化必须先修,哪些弃用警告可以后处理
PHP 8.5 于 2025-11-20 发布。版本比 8.4 小,但开始为 PHP 9.0 收紧。8.5 里被弃用的仍能运行,只是会报警告。升级前先分清两类:以后处理的警告,和部署前必须修的行为变化。
参考链接:
- Upgrading to PHP 8.5 in 2026: the breaking changes worth reading first
- PHP 8.5 发布公告(中文)
- PHP 8.5 迁移指南:新特性
行为变化:会改变结果或语义
float / 类 float 字符串转 int,以及 NAN 转换会警告
不可表示(无法表示为 int)的 float 或类 float 字符串转 int 时仍转换,但发出警告;NAN 转任何类型也警告。金额代码里 (int) $someFloat 溢出是常见触发点。
排查时先定位金额、统计、ID 计算等强制转换点。若输入可能溢出或为 NAN,转换前加范围判断或 is_finite 检查。
非数组解构会警告
对非数组做解构会警告,此前是安静失败。
检查 [$a, $b] = $x 中 $x 的来源。函数返回值、配置项、数据库字段都可能不是数组,必要时提前判断或返回默认结构。
不可比较对象的松散比较语义统一
枚举、CurlHandle 等内部对象与布尔松散比较,双向都遵循 (bool)$object 语义。老代码若靠 $enum == $otherEnum 意外返回 false,可能翻转。比较枚举请用 ===。
全库搜索枚举的 == 比较,改成 ===。内部对象与布尔的松散比较也要用测试覆盖。
PDO FETCH_CLASS 构造参数改为 CUFA 语义,setFetchMode() 中途调用抛 Error
PDO FETCH_CLASS 构造参数改为 CUFA 语义:字符串键当作命名参数,去掉了旧的按值到引用自动包装。用位置参数数组但含字符串键的 hydrate 代码要检查。抓取中途调用 setFetchMode() 现在直接抛 Error。
检查构造参数数组:纯位置参数就用 list,不要混字符串键。把 setFetchMode() 移到执行之前,不要在 fetch 过程中切换。
printf 家族精度处理变化
printf 家族把缺失的精度当作 0;此前重置不正确。带结尾 . 而无数字的格式化,输出宽度可能变化;快照测试能发现。
跑快照测试,重点检查 sprintf 格式串,尤其是 %. 后无数字的写法。
Opcache 总是内建,zend_extension=opcache.so 会警告
Opcache 现在总是内建:--enable-opcache / --disable-opcache configure 参数移除,INI 里显式 zend_extension=opcache.so 会发警告。opcache.enable / opcache.enable_cli 仍可用。Dockerfile 或 Ansible 里硬编码该行的要删掉。
检查 php.ini、Dockerfile、Ansible、构建脚本,删除 zend_extension=opcache.so。确认 opcache.enable 与 opcache.enable_cli 仍按预期配置。
MySQLi 更严:重复构造抛 Error,mysqli_execute() 别名弃用
对已构造的 mysqli 对象再调构造函数现在抛 Error;mysqli_execute() 别名弃用,改用 mysqli_stmt_execute()。
检查单例、容器、连接池里是否重复 new mysqli。把 mysqli_execute() 替换为 mysqli_stmt_execute()。
ArrayObject / ArrayIterator 不再接受枚举
ArrayObject 与 ArrayIterator 不再接受枚举。
传入前把枚举转为标量或字符串,或改存枚举的 backing value。
Intl 至少需要 ICU 57.1
Intl 现在至少需要 ICU 57.1。老发行版需要换基础镜像,而非改代码。
确认基础镜像的 ICU 版本;低于 57.1 就升级镜像。
弃用:会警告,可稍后处理
反引号操作符弃用
反引号操作符(shell_exec 别名)弃用,改用显式调用。
替换为 shell_exec(),同时确认命令拼接与转义安全。
非规范转换名弃用
(integer)、(boolean)、(double)、(binary) 弃用,用短名。
全局搜索这些转换名,改成规范短名。机械改写时先看 diff。
__sleep() 与 __wakeup() 软弃用
__sleep() 与 __wakeup() 软弃用,改用 __serialize() 与 __unserialize()。
搜索魔术方法并迁移序列化实现。序列化策略需要人工决定,工具不会替你选。
非数字字符串自增弃用
$s='a'; $s++; 这种 Perl 风格弃用。
搜索字符串上的 ++,改成显式数字运算或字符串处理。
null 作为数组偏移弃用
$arr[null]=1 以前静默以空串为键,现在弃用。
搜索 [null] 偏移,改成 [''] 或明确键名。
Reflection::setAccessible() 变 no-op 并弃用
自 8.1 默认开放反射,Reflection::setAccessible() 变 no-op 并弃用,可删除调用。
只跑 PHP 8.1+ 的项目直接删除。需要兼容旧版本时,可保留调用并忽略弃用警告。
跨扩展的资源释放函数弃用
curl_close()、imagedestroy()(GD)、xml_parser_free() 以及 MHASH_* 常量弃用——这些类型早已是对象、自行释放。
删除这些释放调用与常量引用,依赖对象生命周期。
排查与处置建议
- 在 CI 里用 PHP 8.5 跑完整测试,打开
E_ALL,收集 deprecation 与 warning。 - 先处理“行为变化”一节:枚举比较、PDO hydrate、printf 快照、mysqli 重复构造、ArrayObject 枚举、ICU 版本。
- 对转换名弃用、反引号等可机械改写的项,先用
--dry-run看 diff 再应用。 - 机械改写不会替你决定序列化策略,也不会修错误的枚举比较,这两类要人工处理。
- 检查部署层:Dockerfile、Ansible、php.ini 中的
zend_extension=opcache.so;确认opcache.enable/opcache.enable_cli配置仍符合预期。 - 对金额、统计等 float→int 点做范围检查;NAN 转换加断言或前置判断。
- 快照测试是 printf 精度变化的第一道防线。