先看这里
PHP 7.4 在 2022 年 11 月 28 日结束支持。针对 PHP2Shell 的修复只以源码 backport 分支的形式存在,没有任何 7.4 发布版本包含这些修复。出路是换到仍受支持的分支。目标版本是 8.4(8.4.27 已修复)。参见 该选哪个版本 和 PHP 7.4。
7.4 到 8.4 之间的改动没法跳过,但可以一次做完。本页按版本列出每一步的变化。每一条都出自 PHP 手册的迁移指南,链接见文末来源。
行得通的顺序
- 备份文件和数据库,并实际做一次恢复测试。
- 把站点复制到运行 8.4、扩展完全相同的预发布 (staging) 环境。
- 把框架、CMS、插件和 Composer 包升级到声明支持 8.4 的版本。运行
composer outdated和composer why-not php 8.4,看是什么在拦着你。 - 用 PHPCompatibility 扫描你自己的代码(命令见下文)。
- 修复扫描结果,先处理 8.0 的条目,因为它们会让代码直接出错。
- 在预发布环境开启
error_reporting = E_ALL和log_errors = On。把每个流程都走一遍,然后看日志。 - 修复 8.1 到 8.4 的弃用 (deprecation) 提示。它们现在不会让代码出错,但会塞满日志,相关功能也可能在之后的版本里被移除。
- 切换生产环境,重启所有 PHP worker,然后 确认版本。
PHP 8.0:会让代码出错的变更
8.0 是大版本,大部分实际问题都出在这里。
- 字符串与数字的比较。
0 == "foo"过去为 true,现在为 false。0 == ""和42 == "42foo"也一样。查找与用户输入或数据库值做松散==比较的地方,改用===。 - 警告变成了异常。 对非数字字符串做算术运算会抛出
TypeError。对不可计数的值调用count()会抛出TypeError。未定义的常量会抛出Error(7.4 会把它当成字符串)。调用内置函数时参数个数不对会抛出ArgumentCountError。 - Notice 变成了 Warning。 读取未定义的变量、数组键或属性,现在会记录一条 warning。旧主题和旧插件会用这类提示把日志塞满。
- 移除的函数。
each()、create_function()、__autoload()、money_format()、get_magic_quotes_gpc()和fgetss()都已删除。改用foreach、匿名函数和spl_autoload_register()。 - 移除的语法。
$str{0}(改用$str[0])、(real)和(unset)类型转换,以及与类同名的构造方法(改用__construct())。 - 字符串拼接的优先级。
"Sum: " . $a + $b现在先做加法。原本想要旧顺序的地方请加括号。 - 命名参数。
call_user_func_array()现在会把字符串键当作参数名,以前是忽略。检查传入关联数组的调用。 - 资源变成了对象。
curl_init()返回CurlHandle,GD 和 OpenSSL 函数同样返回对象。检查is_resource($ch)的代码现在会判断失败,改为检查false。 @不再屏蔽致命错误。 错误处理函数里判断error_reporting() == 0的地方,应改为判断error_reporting() & $errno。- 默认值。
error_reporting默认是E_ALL。PDO 的默认错误模式改为抛出异常。 - 保留名称。
match成了关键字,mixed不能再用作类名。#[现在开始一个注解 (attribute),以前是注释。 - 扩展与模块。
mbstring.func_overload已删除,XML-RPC 移到了 PECL。Apache 模块名从php7_module改成了php_module,旧的LoadModule和IfModule配置行因此不再匹配。 - 8.0 中弃用的写法。 可选参数之后的必选参数、
libxml_disable_entity_loader(),以及返回布尔值而非整数的排序回调。在usort()里用<=>。
PHP 8.1:大多是弃用,有两处行为变化
- 向内置函数传
null。 给不可为空的参数传null,例如str_contains($x, null)或trim($x),会记录一条弃用提示。在旧 WordPress 代码里,这是日志噪音的最大来源。用(string) $x转换,或者写$x ?? ''。 - mysqli 会抛异常。 默认错误模式从静默改成了抛出异常。依赖静默失败的调用要包上异常处理,或者用
mysqli_report(MYSQLI_REPORT_OFF);恢复旧模式。 htmlspecialchars()的默认参数 改成了ENT_QUOTES | ENT_SUBSTITUTE,所以'会变成'。- 内部方法的返回类型。 实现
ArrayAccess、Iterator、Countable或JsonSerializable的类,需要声明兼容的返回类型,或者加上#[\ReturnTypeWillChange]注解,否则会出现弃用提示。 - 只实现
Serializable的写法已弃用。补上__serialize()和__unserialize()。 - 其他弃用。
strftime()、FILTER_SANITIZE_STRING、把false当数组往里追加元素,以及会丢失精度的隐式浮点转整数,例如$a[15.5]。 - PDO 使用 MySQL 和 SQLite 驱动时,现在以原生类型返回整数和浮点数。
PHP 8.2:动态属性
- 动态属性已弃用。 在从未声明
newField的类上执行$obj->newField = 1,会记录一条弃用提示。请声明该属性。临时办法是用#[\AllowDynamicProperties]。stdClass不受影响。 "${var}"形式的字符串插值 已弃用。改写为"{$var}"或"$var"。utf8_encode()和utf8_decode()已弃用。改用mb_convert_encoding()。- 相对可调用字符串,例如
"self::method",已弃用。改用[self::class, 'method']。 - 大小写转换函数,例如
strtolower()和ucfirst(),现在忽略 locale。处理本地化文本请用mb_*系列函数。 str_split('')返回空数组,以前返回[""]。
PHP 8.3:改动很少
range()更严格了。步长为0、递增范围使用负步长,以及传入 INF 或 NAN,都会抛出ValueError。number_format()在$decimals为负数时,现在会在小数点之前的位上舍入。- 数组的第一个索引为负数时,下一个隐式键会变化。先赋值
$a[-5]再执行$a[],得到的键是-4,以前是0。 - 日期相关错误 现在使用
DateException和DateError类,以前是 warning。 - 已弃用。 不带参数调用
get_class(),assert_options()和assert.*系列 INI 设置,以及只传一个参数的ReflectionProperty::setValue()。
PHP 8.4:可空参数的弃用
- 隐式可空参数。
function f(Foo $a = null)已弃用,改写为?Foo $a = null。旧库每次加载都会触发这条提示,所以先升级依赖包,再评估你自己的代码。 E_STRICT已删除,带E_USER_ERROR的trigger_error()已弃用。改为抛出异常。- CSV 转义字符。 依赖
fgetcsv()、fputcsv()和str_getcsv()的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及相关设置已弃用,SID常量同样已弃用。 round()遇到无效的舍入模式会抛出ValueError。旧的预舍入步骤已删除,少数结果会因此改变。- bcrypt 的 cost 默认值从 10 改为 12。登录会变慢,已有的哈希仍能正常校验。
- OPcache JIT 的默认值变了。如果你只靠
opcache.jit_buffer_size开启 JIT,还需要同时设置opcache.jit。 - 新名称可能冲突。 8.4 新增的任何函数或类,都会让定义了同名函数或类的代码出错。把你自己的辅助函数包在
function_exists()判断里。 - 依赖库的最低版本提高了。 手册列出的是 libcurl 7.61.0、libxml2 2.9.4 和 OpenSSL 1.1.1。很老的服务器可能达不到。
扫描与修复命令
在工作机或 CI 里运行这些命令,别在 Web 服务器上跑。
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 7.4-8.4 \
--ignore='*/vendor/*,*/node_modules/*' --report-full=compat-report.txt .
# 切换之后:只针对 8.4
vendor/bin/phpcs -ps --standard=PHPCompatibility --runtime-set testVersion 8.4 \
--ignore='*/vendor/*,*/node_modules/*' .
上面的版本约束取自 2026 年 10 月 22 日 PHPCompatibility README 中的写法。当前的约束请以 README 为准。PHPCompatibility 10 需要 PHP_CodeSniffer 4.0.2 或更高版本。
Rector 能自动完成一部分修复。提交之前先看一遍它生成的 diff。
composer require rector/rector --dev
vendor/bin/rector # 提示是否创建 rector.php
vendor/bin/rector process --dry-run # 预览
vendor/bin/rector process # 应用修改
<?php
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPaths([__DIR__ . '/src'])
->withPhpSets(); // 从 composer.json 读取目标 PHP 版本
这两个工具都替代不了预发布环境里的测试。它们只读代码,从不执行代码。不属于你的插件和主题代码别去动,升级它们就行。WordPress 用户请看 更改 PHP 版本。
最后审阅:2026年10月22日