两件事,按这个顺序做
PHP 8.2 的安全修复持续到 2026 年 12 月 31 日,从今天算大约还有十周。之后不会再有官方版本。见 PHP 8.2。
- 今天: 安装 8.2.35。它包含 CVE-2026-109618 和 CVE-2026-109619 的 PHP2Shell 修复。重启所有 PHP worker,并 确认正在运行的版本。
- 年底之前: 迁到 8.4(在 8.4.27 中修复)。它的安全修复持续到 2028 年 12 月 31 日。对比见 选哪个版本。
就算第二件事要花几周,也先把第一件做了。同一分支内的补丁版本改动很小。跨小版本升级则需要 staging 环境和测试。
如果你的发行版或主机商还没有推出 8.2.35,修复 下面的各软件包指南列出了每家厂商已经发布的版本。用虚拟主机的话,去问主机商,并参考 虚拟主机。
升级的操作顺序
- 备份文件和数据库。测试一次恢复。
- 在 8.4 上搭一个 staging 副本,扩展和
php.ini设置保持一致。 - 把 CMS、框架、插件和 Composer 包更新到声明支持 8.4 的版本。
composer outdated和composer why-not php 8.4会显示是什么在卡着你。 - 运行下面的兼容性扫描。
- 在 staging 上设置
error_reporting = E_ALL和log_errors = On。测试登录、表单、上传、结账和定时任务。看日志。 - 切换生产环境,重启所有 PHP worker,确认版本。
- 几天之内保留在面板里一键切回 8.2 的能力。
你在 8.2 上的日志里已经能看到 8.2 的废弃警告,主要是动态属性和 utf8_encode()。先修这些,因为它们在 8.4 上依然是废弃状态。
PHP 8.3 的变更
8.3 的破坏性改动不多。下面这些是实际项目里会碰到的。
range()更严格了。 步长为0、递增区间用负步长,以及无穷大或 NAN,都会抛出ValueError。range(5, 'z')这样的混合调用会产生警告。number_format()在$decimals为负数时,现在会在小数点前进行舍入,以前是直接忽略。- 负数数组下标。 对空数组执行
$a[-5] = 'x'之后,下一个$a[]的下标是-4,以前是0。 - 日期错误 现在使用
DateError和DateException类,不再是警告。检查用try/catch包住日期解析的代码。 - 深度递归 在调用栈接近上限时可能抛出
Error。 - 已废弃: 不带参数的
get_class()和get_parent_class(),assert_options()以及assert.*INI 设置,只传一个参数的ReflectionProperty::setValue(),还有MT_RAND_PHP模式。 - 对非数字字符串做自增 已被软废弃(soft deprecated)。新代码请用
str_increment()。 - 已移除: INI 指令
opcache.consistency_checks。把它从php.ini里删掉,否则启动时会有提示。
PHP 8.4 的变更
- 隐式可空参数。
function f(Foo $a = null)已废弃,要写成?Foo $a = null。过时的库会在每个请求里触发这条警告。先更新软件包,再评估你自己的代码。 E_STRICT作为错误级别已移除。 带E_USER_ERROR的trigger_error()已废弃,改为抛出异常。- CSV 函数。 依赖
fgetcsv()、fputcsv()和str_getcsv()的escape参数默认值已废弃。请显式传入escape。 - 移到 PECL: IMAP、OCI8、PDO_OCI 和 PSpell。如果你调用了
imap_*函数,要安装 PECL 包或者换一个库。 - Session 设置。 修改
session.sid_length、session.sid_bits_per_character、session.use_only_cookies、session.use_trans_sid、session.trans_sid_tags、session.trans_sid_hosts或session.referer_check已废弃。SID常量也一样。 - mysqli:
mysqli_ping()、mysqli_kill()和mysqli_refresh()已废弃。 round()遇到无效模式会抛出ValueError,旧的预舍入逻辑也被删掉了,所以少数边界情况的结果会变。exit()的行为和函数一样。 它遵守strict_types,类型无效时抛出TypeError。- JIT 默认值变了。 如果你只通过
opcache.jit_buffer_size开启 JIT,还需要设置opcache.jit。 - bcrypt cost 默认值从 10 改为 12。登录会变慢一些。已有的哈希仍然可以验证。
- 命名冲突。 8.4 新增的函数或类,会让定义了同名函数或类的代码出错。用
function_exists()保护你自己的辅助函数。 - 最低库版本: libcurl 7.61.0、libxml2 2.9.4 和 OpenSSL 1.1.1。检查一下老服务器。
mod_php最低需要 Apache 2.4。
扫描和修复命令
在开发机或 CI 里运行。
composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
composer require --dev phpcompatibility/php-compatibility:"^10.0.0@dev"
vendor/bin/phpcs -i
vendor/bin/phpcs -ps --standard=PHPCompatibility --runtime-set testVersion 8.2-8.4 \
--ignore='*/vendor/*,*/node_modules/*' --report-full=compat-report.txt .
范围 8.2-8.4 会报告影响其中任一版本的问题,这样两个版本并行运行期间代码都是安全的。切换完成后,改用 --runtime-set testVersion 8.4。^10.0.0@dev 这个约束取自 2026 年 10 月 22 日 PHPCompatibility 的 README,需要 PHP_CodeSniffer 4.0.2 或更新版本。
Rector 可以修掉其中一部分问题,比如显式可空类型。提交之前先看 diff。
composer require rector/rector --dev
vendor/bin/rector process --dry-run
vendor/bin/rector process
在 rector.php 里写 return RectorConfig::configure()->withPaths([__DIR__ . '/src'])->withPhpSets();。这样 Rector 会从 composer.json 读取 PHP 版本。
别人写的插件或主题,不要去改它们的代码,更新它们就好。WordPress 用户见 更改 PHP 版本。
最后审阅:2026年10月22日