2020年WordPress和PHP 8兼容性报告

11月26日,PHP 8将向全世界发布。PHP 8将成为PHP历史上最具突破性的PHP版本之一,它将为WordPress等传统PHP代码库带来前所未有的挑战,以解决兼容性问题。 

今天,我们为您带来有关WordPress和PHP 8兼容性的全面报告。在分享这些内容时,我们希望对WordPress和PHP社区进行教育并帮助其了解WordPress和PHP 8的状态。毕竟,PHP是为WordPress提供支持的技术,而WordPress是迄今为止最大的PHP使用者。

该报告是由Juliette(在PHP和WordPress社区中广受尊敬的PHP工程师),Herre(Yoast的首席软件架构师)和我本人共同努力的结果。

介绍

此报告中有什么内容?

在本报告的第一部分中,我们将概述PHP 8中的更改,这些更改可能会严重影响WordPress和其他旧版代码库。在本报告的第二部分中,我们将尝试提供有关WordPress和PHP兼容性的过去,现在和未来挑战的观点。在报告的末尾,我们包含了Yoast.com的案例研究,以说明PHP 8上的大型WordPress网站可能发生的问题。 

PHP 8中为什么会有如此多的重大更改?

PHP 8是PHP的主要更新,通常的做法是从先前的次要版本范围中删除主要版本中的弃用版本。对于PHP 8,在先前的7. *版本中已弃用了许多重大更改。因此,对于经过多年努力修复不推荐使用的API的项目,根本就不难升级。 

但是,与以前的PHP版本相比,PHP 7. *版本的弃用范围更大。从PHP 5.6到PHP 7是一个相对简单的迁移,从7.x到8的迁移可能会非常痛苦,尤其是对于非常老的代码库,例如WordPress和许多可用的插件。对于类型正确的代码库或最新的PHP版本保持最新的代码库,没有太大的问题。但是,现实情况是WordPress不是这样的代码库。

WordPress是否已经与PHP 8兼容?

嗯,是。有点。也许。我们高度怀疑。真的不可能讲。

WordPress旨在始终与新版本的PHP兼容Sergey在解决可以使用可用策略检测到的大多数兼容性问题方面做得非常出色。我们绝对会更深入地探讨这些内容以及存在的问题。从技术上讲,当前每晚的WordPress与PHP 8的兼容性与我们在新版本PHP出现之前从WordPress版本获得的兼容性处于同一水平。我们认为测试的范围非常广泛,修复过程非常艰苦,而且修复的程度与WordPress核心内的任何一轮PHP兼容性修复一样高。另请参阅在WordPress.org上进行PHP 8测试的电话。

但是,不幸的是,这次我们一直做的事情不会减少。PHP 8中包含的大量重大更改和更改类型,以及跨版本工具中增加的一些复杂性,使得这种兼容性挑战与我们以前看到的事物截然不同。该报告旨在解释情况。

第1部分:PHP 8中最令人担忧的重大更改

严格在PHP 8中进行内部输入

PHP 8中最重要的突破性变化之一与严格的输入有关。PHP中的用户定义函数已引发TypeError。但是,内部函数发出警告并返回null。PHP 8使这种一致,内部函数现在也抛出TypeError。这不仅会影响PHP 8之前已经发出警告的函数,还会影响魔术方法(以前没有进行类型检查)和引入了类型声明的函数。因此,不可能通过修复PHP 7.4环境中的类型警告来捕获此更改引起的所有问题。以下是相关重大更改的概述,这些更改共同定义了PHP 8中与严格类型相关的更改的范围。

一致的类型错误

从PHP 8开始,内部函数现在为所有类型的参数抛出TypeError。

算术运算符类型检查

算术和按位运算符+,-,*,/,**,%,<<,>>,&,|,^,〜,++,-现在将在一个操作数为数组,资源或非重载对象。唯一的例外是array + array union操作,该操作仍受支持。

在PHP 8之前,可以在数组,资源或对象上应用算术或按位运算符。这是不可能的,它将引发TypeError。

魔术方法类型检查

现在,Magic Methods将对其参数和返回类型进行检查(如果已声明)。签名应与以下列表匹配:

  • __call(字符串$ name,数组$ arguments):混合
  • __callStatic(字符串$ name,数组$ arguments):混合
  • __clone():无效
  • __debugInfo():?array
  • __get(字符串$ name):混合
  • __invoke(混合$ arguments):混合
  • __isset(字符串$ name):布尔
  • __serialize():数组
  • __set(字符串$ name,混合$ value):空
  • __set_state(数组$ properties):对象
  • __sleep():数组
  • __unserialize(数组$ data):无效
  • __unset(字符串$ name):无效
  • __wakeup():无效

数字字符串处理

数字字符串处理已更改为更加直观且不易出错。现在允许在数字字符串中使用尾随空格,以与如何处理前导空格保持一致。这主要影响:

  • is_numeric()函数
  • 字符串之间的比较
  • 类型声明
  • 递增和递减运算

“前导数字字符串”的概念已被删除。为了简化迁移而存在的情况。现在,发出E_NOTICE“遇到一个格式错误的数值”的字符串将发出E_WARNING“遇到一个非数值值”,并且发出E_WARNING“遇到一个非数值值”的所有字符串现在都将引发TypeError。这主要影响:

  • 算术运算
  • 按位运算

从E_WARNING到TypeError的更改也会影响E_WARNING对于非法字符串偏移的“非法字符串偏移’string’”。从字符串到int / float的显式强制转换的行为没有变化。

命名参数

还增加了对命名参数的支持。这有两个主要含义:

  1. 重命名参数成为一项重大更改。如果重命名参数,则使用命名参数调用该函数的任何地方都会中断。
  2. call_user_func_array()的行为发生了变化。以前,可以使用关联数组调用call_user_func_array()。现在,传递关联数组将被解释为使用命名参数,如果不存在任何命名参数,这将引发Exception。

API更改可能导致类型错误

下面我们列出了一些API更改示例的列表,这些示例将导致类型或参数错误,而以前的PHP版本中没有此类指示。

  • mktime()和gmmktime()现在至少需要一个参数。time()可用于获取当前时间戳。
  • spl_autoload_register()现在将始终在无效参数上引发TypeError,因此第二个参数$ do_throw将被忽略,如果将其设置为false,则会发出通知。
  • assert()将不再求值字符串参数,而是将它们视为任何其他参数。应该使用assert($ a == $ b)来代替assert(’$ a == $ b’)。assert.quiet_eval ini指令和ASSERT_QUIET_EVAL常量也已删除,因为它们将不再起作用。
  • vsprintf(),vfprintf()和vprintf()的$ args参数现在必须是一个数组。以前,任何类型都可以接受。
  • 具有在运行时解析为null的默认值的参数将不再隐式将参数类型标记为可为空。请使用显式可为空的类型,或者使用显式为​​空的默认值。

警告转换为错误异常

在PHP 8中,有许多PHP警告已更改为错误异常。 

与RFC无关的错误级别更改

以下警告已转换为可能与PHP 7.x版本中的弃用相关的错误:

  • 尝试写入非对象的属性。以前,这会为null,false和空字符串隐式创建一个stdClass对象。
  • 尝试将元素追加到已经使用PHP_INT_MAX键的数组中。
  • 尝试使用无效的类型(数组或对象)作为数组键或字符串偏移量。
  • 尝试写入标量值的数组索引。
  • 尝试打开非数组/ Traversable的包装。

重新分类的发动机警告

以前仅触发警告或通知的许多错误已转换为错误。进行了以下更改:

  • 未定义的变量:警告而不是通知
  • 未定义的数组索引:警告而不是通知
  • 除以零:DivisionByZeroError异常而不是警告
  • 尝试增加/减少非对象的属性’%s’:错误异常而不是警告
  • 尝试修改非对象的属性’%s’:错误异常而不是警告
  • 尝试分配非对象的属性’%s’:错误异常而不是警告
  • 从空值创建默认对象:错误异常而不是警告
  • 试图获取非对象的属性’%s’:警告而不是通知
  • 未定义的属性:%s :: $%s:警告而不是通知
  • 无法将元素添加到数组,因为已经占用了下一个元素:错误异常而不是警告
  • 无法取消设置非数组变量中的偏移量:错误异常而不是警告
  • 无法将标量值用作数组:错误异常而不是警告
  • 只能解压缩数组和Traversables:TypeError异常而不是警告
  • 为foreach()提供了无效的参数:TypeError异常而不是警告
  • 非法的偏移量类型:TypeError异常而不是警告
  • isset中的偏移量类型非法或为空:TypeError异常而不是警告
  • 未设置无效的偏移类型:TypeError异常而不是警告
  • 数组到字符串的转换:警告而不是通知
  • 资源ID#%d用作偏移量,转换为整数(%d):警告而不是通知
  • 发生字符串偏移量强制转换:警告而不是通知
  • 未初始化的字符串偏移量:%d:警告而不是通知
  • 无法将空字符串分配给字符串偏移量:错误异常而不是警告
  • 提供的资源不是有效的流资源:TypeError异常而不是警告
  • #@运算符不再使致命错误失效。
    可能此更改可能会揭示在PHP 8之前再次隐藏的错误。请确保在生产服务器上将display_errors = Off设置为Off!

不兼容的方法签名的致命错误

由于两个类之间的方法签名不兼容而导致的继承错误现在将引发致命错误,而不是警告。

默认错误报告级别

使用PHP 8,默认错误报告级别更改为E_ALL,而不是E_NOTICE和E_DEPRECATED之外的所有内容。这意味着许多错误将开始出现,这些错误以前是被忽略的。

7.x弃用

在PHP 7.x发行周期中,每个版本都引入了新的弃用方法,这些弃用方法现已作为PHP 8中的功能删除而完成。这也适用于PHP 5.x中已经存在但并未删除的某些弃用方法。在PHP 7.0版本中。

最值得注意的是,PHP 8中已删除了以下已过时的功能:

  • $ php_errormsg
  • create_function()
  • mbstring.func_overload
  • 没有第二个参数的parse_str()
  • 每()
  • 带有字符串参数的assert()
  • 错误处理程序的$ errcontext参数
  • 带整数针的字符串搜索功能
  • 定义独立的assert()函数
  • 实型(浮点数别名)
  • 魔术报价遗留
  • 具有对象的array_key_exists()
  • 反射export()方法
  • implode()参数顺序混合
  • 从非静态闭包中解除$ this的绑定
  • restore_include_path()函数
  • allow_url_include ini指令

其他重大变化。

PHP 8中还有许多其他(重大)更改,包括对精选但经常使用的函数的返回类型进行了重大更改,将资源转换为Value Objects等。GDOpenSSLSocketsXMLZlibsubstr()等的示例。

上面我们试图强调那些可能以重大方式直接影响WordPress(以及许多其他旧系统)的系统。我们基于对优秀导游“这个概述什么是在PHP 8个新的” stitcher.ioPHP 8升级指南。有关更多信息,请参考这些资源。

第2部分:兼容性挑战

为了使现有代码库与新版本的PHP兼容,可以部署两种不同的发现策略:

  • 静态分析工具(如PHPCompatibility)可检测语法问题。
  • 自动化测试以检测运行时问题。
  • 手动测试以检测运行时问题。

根据测试套件的覆盖范围以及运行时和语法更改的比例,这些策略可作为固定代码库与新版本PHP兼容性的良好基础。但是,对于WordPress和PHP 8,还有很多其他挑战,这使得很难依靠这些策略来修复兼容性并声明WordPress与PHP 8兼容。下面我们将报告针对哪些策略进行了部署WordPress及其结果。

静态分析工具

由于PHP 8.0中某些更改的性质,使用静态分析可以发现的问题是有限的。在那些静态分析试图超越其传统功能并试图跟踪运行时类型以及变量和常量的值的情况下,此类扫描的结果将很容易出现误报。

除此之外,PHPCompatibility是唯一用于查找与PHP跨版本兼容性相关的问题的静态分析工具。

其他静态分析工具将报告更大范围的问题。遍历结果以查找与PHP跨版本兼容性相关实际上正确的问题,这非常耗时,并且需要对工具进行深入了解才能将其配置为噪声最小。

同时,这些工具不断变化,试图跟上PHP的变化并更新可用的扫描,因此,与当前所发现的内容无关,这些工具很可能会发现更多的问题。在不远的将来。

使用PHP兼容性扫描WordPress

在WP 5.6开发周期的早期报告的问题,自基于PHPCompatibility扫描以来已修复:

  • 在必需参数之前声明的可选参数
  • 最终私人方法
  • 删除对create_function的使用
  • 仅有条件地调用libxmldisableentity_loader

PHPCompatibility检测到的另一个PHP 8问题是“ __destruct()将不再在__construct()中的die()之后被调用”。这是由扫描仪正确检测到的,但经进一步分析,确定在这种特定情况下没有问题。

PHPCompatibility还检测到插件/主题编辑器使用的代码中存在问题。对所涉及代码的分析已确定代码中存在基础监督。WordPress尝试对编辑器中的代码进行最少的分析,但未考虑PHP 5.3+(命名空间)代码。现在,在考虑到PHP 8.0的相关更改的同时,将使解决此监督变得更加复杂。

使用PHPCompatibility进行的扫描已在开发版本中运行。尚未发布包含PHP 8特定扫描的版本。

那里报告了扫描仪检测到的外部维护依赖关系中的问题。

使用Exakat扫描WordPress

截至10月16日基于WP干线的最新公共扫描,Exakat总共报告了149.567个问题。

Exakat仪表板屏幕截图

PHP 8兼容性报告总共包含93个问题,但是由于报告中尚未包含与PHP 8相关的许多分析,因此该报告尚不完整。

审查报告

基于PHP 8.0兼容性报告的“最糟糕”的违规行为是子类中方法声明中的参数与父类中的参数命名不同。这与新的PHP 8.0“函数调用中的命名参数”功能不兼容。

已在问题Trac 51553中报告了此问题,并且已在票证上附加了对此补丁程序,但尚未提交。

此故障单中还列出了应执行以准备函数调用中命名参数的其他任务,包括这些任务之一的操作列表。到目前为止,尚未对任何这些措施采取任何措施。

12个“带运算符的不受支持的类型”警告大部分为误报,并且已报告给Exakat。

更令人担忧的是,报告了使用错误的参数类型的14.679问题,调用错误的类型的14.135问题,参数数量的错误的15.605问题,本机PHP函数的801错误类型以及25个错误的参数类型问题。

尽管由于WordPress不使用类型声明,因此这些报告将包含大量误报,因此类型是从找到的代码和docblock中指示的类型推断出来的,但仍应单独检查这些问题。即使发现的问题中只有1%是正确的,这仍然可以归结为约450个仍需处理的错误。除了从假阳性中剔除实际问题之外,还需要花费大量时间。在撰写本文时,作者尚未意识到正在努力检查这些报告并确定并解决这些问题。

已检查了Exakat针对与PHP 8相关的其他问题的一些分析报告,这些报告未包含在PHP 8.0兼容性报告中。这些补丁已在上周提交并提交。这包括:

  • WP版本模块中的PHP 8.0致命错误的修复程序。
  • pomo库中针对PHP 8.0警告的修复程序。

使用PHPStan扫描WordPress

使用PHPStan进行的扫描需要高度自定义的规则集,以获取可远程使用的结果,即使如此,它们也充满了误报,使输出无法使用。

注意:这不一定是对PHPStan工具的批评,而是很大程度上是由于WordPress几乎不使用类型声明,而PHPStan主要针对使用现代代码的项目。

使用最基本的配置进行初始扫描会产生超过20.000个问题。

使用上面提到的高度定制的规则集进行的扫描(具体针对PHP 8相关问题),在第5级时仍产生580个问题,在第7级时仍产生了2.150个潜在问题,尽管这些问题可能包含很多误报,但还有380个问题同样要注意第8级。

一个Trac的门票被打开了,而回,以解决的问题清单基于一个未知的配置,但专门针对传递的参数类型不匹配(5级)。有一份可解决这些问题的PR草案。

但是,对该PR的初步评估表明,提出的大多数修补程序都可以通过将变量类型转换为期望的类型来隐藏问题,而不是通过进行适当的类型检查来实际解决它们。如果这些更改没有进行严格的单元测试(也没有进行),则可能导致应用程序出现意外行为。这也可能导致更难以调试进一步的错误。

目前,尚未验证所提议的修复程序是否必要,还是所确定的问题应被视为误报。

扫描使用PHPStan 0.12.52运行。

测验

由于PHP 8.0中有问题的更改的性质,静态分析只能走这么远。手动检查和测试软件是一项艰巨的工作,当有很多需要注意的地方时,人们很容易忽略事物。

最终用户执行的手动测试往往相对没有用,因为这通常只会导致测试“快乐之路”。需要进行全面的探索性测试和回归测试才能获得更可靠的结果。即使发现问题,也需要进行大量调试才能找出问题的原因-WordPress?插件主题?-是否与PHP兼容性有关。

最重要的是拥有高质量的自动化测试并在PHP 8上运行它们,因为这将最好地表明PHP 8.0问题。

在PHP 8上运行自动化测试

在PHP 8上运行一个自动化测试套件可以使我们无处可挡,因为它实际上是PHP世界中用于单元测试的工具,PHPUnit通常每年都会发布一个主要版本,并且每个主要版本都支持较旧的PHP版本和引入重大变化。正式与PHP 8.0兼容的第一个PHPUnit版本是2020年8月发布的PHPUnit 9.3。

由于WordPress仍至少支持PHP 5.6,要在PHP 8.0上运行测试,任何与WordPress相关的测试套件都必须与PHPUnit 5兼容,直至PHPUnit 9。

虽然正在构建工具来帮助解决此问题(请在下周查看有关此问题的博客文章!),但仍需要花费时间和精力来实现这些工具并使测试套件兼容。与实际解决PHP 8相关问题的时间相比,节省了时间。

使测试在WP Core的PHP 8上运行

WP Core的测试针对PHP 8运行,目前正在通过。尽管PHPUnit 9.3是最早正式与PHP 8.0兼容的PHPUnit版本,但这些测试仍在PHPUnit 7.5的作曲家安装版本上运行。

通过将选定数量的文件/类从PHPUnit 9.3复制到WP测试套件中(而不是从Composer自动加载生成中删除PHPUnit本机类),从而支持在WP测试套件中使用PHPUnit 9.3的副本,已经克服了最后一点。虽然目前可以使用,但除了当前可能需要的维护之外,这是一个棘手的解决方案,将来可能无法持续。

注意:虽然所有其他WordPress Core CI构建都使用Phar版本的PHPUnit,但是在PHP 8.0上运行PHPUnit 7.5时这是不可能的,因为不再维护PHPUnit 7并且Phar包含不兼容的PHPUnit依赖项。使用基于Composer的PHPUnit安装可以解决此问题,因为它将引入最新的兼容版本的依赖关系,尽管需要–ignore-platform-reqs 

至于测试的质量,这从一开始就相对较低,大多数情况下使用松散类型检查。

来解决这一Trac的票是在2016年在PHP 8.0已经打开,着眼于严格遵守类型,这个票已经复苏,大量的工作已经完成,以减轻这一点。

在撰写本文时,仍有近800个实例(676 assertEquals()+ 96 assertNotEquals())仍在使用松散类型检查-低于8000个实例。

在比较对象时,部分保留的松散类型声明是合法的,部分仍然需要解决,但当前会导致测试失败。这些最后一个突出显示了测试中的缺点,但在测试的代码中却更多。

代码覆盖率

在评估当前测试套件的价值时,重要的是要查看测试套件的代码覆盖率并了解代码覆盖率是如何工作的。

可以通过两种方式报告代码覆盖率: 

  • 不干净
  • 清洁(严格)

为了解释这种差异以及为什么这种差异很重要,我们采用一个简单的代码示例: 

function bar() {
    // Do something
}

function foo() {
    $bar = bar();
    // Do something more.
}

现在,如果对方法foo()进行了测试,并且代码覆盖率未设置为严格,则上述代码的代码覆盖率将报告为100%,因为在运行测试时将同时调用这两种方法。

相反,如果使用@covers标签,则上述代码的代码覆盖率将报告为50%(仅方法foo())。

在发现与PHP 8.0相关的问题时,这种区别非常重要,因为采用专用测试的方法将-如果操作正确-将进行“压力测试”,即使用不同类型和输入值进行测试,以确保该方法正确处理了意外情况。

相反,没有专用测试,但是“偶然地”被测试覆盖的方法仅会针对“快乐之路”进行测试,即给出结果,以便可以继续进行针对实际测试方法的测试。

由于PHP 8.0中大多数新的致命错误都与“不快乐路径”相关,因此对所有代码进行专用测试以及覆盖这些“快乐路径”和“不快乐路径”的专用测试非常重要。

通过改进测试套件以使用@covers标记,可以清楚知道代码库的哪一部分没有专用测试。从代码库中没有专用测试的最重要部分开始,这将为扩展测试套件设置明智的优先级。

四年前,已经出现一个问题来引起人们对缺少的@covers标签的关注。鉴于PHP 8,该休眠票证也已恢复,并且在过去几周内提交了许多添加@covers标签的补丁程序。到目前为止,这些都没有落实。

目前,WordPress的代码覆盖率大都是“不干净”的。今年8月的一次扫描显示不干净的代码覆盖率为39.82%。本周的扫描显示,不干净的代码覆盖率为40.32%,提高了0.5%。

根据以前的经验,不干净的代码覆盖率40%可能会转换为大约15-20%的实际代码覆盖率,从而使60%的WordPress核心代码目前完全未经测试,而另外20%或更多的代码仅针对“快乐之路”进行了测试。当与PHP 8中的大量重大更改联系在一起时,这将成为高度关注的问题。基本上不能依靠自动测试作为检测兼容性问题的策略。

注意:严格的代码覆盖率运行仅记录WordPress测试套件中带有@covers标记的那些测试的覆盖率,当前显示6.8%的代码覆盖率,因此可以视为绝对最小值。

测试插件和主题

只有一小部分可用的插件(比较流行和专业开发的插件)具有自动测试功能。一般来说,这令人担忧,每个WordPress网站运行的插件介于30到100个之间。

对于主题,实施自动测试的情况更加罕见。

允许这些测试套件在PHP 8.0上运行具有挑战性,甚至在获得关于这些插件和主题的PHP 8兼容性的见解之前。

但是,最重要的是,经过测试的插件/主题很可能会因为使用专业的开发模型而预期出现的PHP 8.0问题最少。

大量未经测试的插件和主题引起更多关注,因为在PHP 8上运行时,这些插件和主题更有可能出现问题。

对于确实具有测试的插件(和主题),主要有两种类型的测试,它们可能有也可能没有。

  • 整合测试。这些测试是在运行测试套件之前先加载WordPress本身的测试,并且这些测试将使用WordPress核心代码并与WordPress测试套件集成。
  • 单元测试。独立测试通常使用诸如MockeryBrainMonkey之类的流行框架来“模拟” WordPress,以测试插件代码。

整合测试

WordPress本身已决定坚持使用PHPUnit 7.5,这意味着对于插件和主题的集成测试,这些也将绑定到PHPUnit 7.5(最大)。

插件和主题要么需要复制WordPress核心中的hack才能运行其集成测试,要么需要使用WordPress核心中的文件,但是由于无法使用相同的Composer自动加载生成功能,因此它们需要创建自定义自动加载器骇客。

这样的自定义自动加载器将需要在Composer自动加载文件之前被引导以防止仍然加载PHPUnit本机文件。

单元测试

对于使用BrainMonkey或Mockery进行的单元测试,需要PHPUnit> 8,因为可用于PHPUnit 7.x的Mockery框架(也由BrainMonkey使用)与PHP 8.0不兼容。这意味着必须使这些测试套件与PHPUnit 5最高兼容9。这也增加了另一个挑战,因为当同时使用两种类型的测试套件时,需要不同版本的PHPUnit来运行每个测试套件。

为了加剧这种情况,插件通常将具有已提交的composer.lock文件,以确保其运行时依赖项处于它们可以依赖的特定版本并与PHP 5.6兼容。这最后一部分通常是通过使用以下平台来实施的:其composer.json文件中的php 5.6配置类型。但是,这也意味着它们的开发依赖项(例如PHPUnit,BrainMonkey,Mockery)将被锁定在与PHP 5.6兼容的版本上,这将阻止测试在PHP 8.0上运行。

可以通过动态删除平台设置并更新composer.json和composer.lockfiles来解决此问题,但这确实使在PHP 8.0上运行测试更加复杂,无论是CI还是本地开发人员,更不用说外部贡献者了。

外部WordPress依赖项的兼容性

WordPress与PHP 8的部分兼容性还取决于其外部PHP依赖关系。尽管基于以上列出的策略,WordPress的最新夜间版本似乎与PHP 8兼容(并带有必要的警告),但对于外部依赖项还不能这么说。

外部依赖关系(已维护)

目前,外部依赖项或WordPress Core(如GetID3,PHPMailer和Requests)的PHP 8兼容性状态未知。

  • 对于未发布的2.0分支,GetID3仅有1个测试。此测试不在CI中运行。代码覆盖率基本上是0%。许多基于静态分析发现的与PHP 8相关的修复已被合并,但尚未包含在标记的发行版中。
  • PHPMailer进行了测试(+/- 73%的不干净代码覆盖率),但是这些测试尚未针对PHP 8运行。已经与图书馆的维护者建立了联系,并有望在下个星期左右进行此项工作。到目前为止,已报告并修复了一个PHP 8问题。该修复程序在标记的版本中尚不可用。
  • 请求具有测试(+/- 90%的不干净代码覆盖率)。在针对PHP 8运行测试时,由于使用的PHPUnit版本与PHP 8不兼容,测试失败。在修复测试套件之前,不会知道实际的PHP 8状态。该项目自2016年以来没有发布过。维护者在很大程度上放弃了该项目,但是对任何抱怨它的人都拥有提交权。这些“抱怨者”中的两个目前正在协作,至少要获得一个新版本的标签,以及使测试能够正确地在PHP 8上运行,但这是由于纯粹的绝望和维护者放弃此回购而产生的暂时情况仍然是一个关注点。
  • SimplePie进行了测试,尽管代码覆盖率未知。这些测试正在PHP 8上运行。1.5.6发行版中包含并合并了许多与PHP 8相关的修补程序。WordPress已更新至SimplePie 1.5.6。当前在PHP 8上有一个未解决的测试失败。
  • 与Random_CompatSodium_Compat无关。除了对它们进行很好的测试之外,使用PHP 8.0时不会调用这些兼容层中的代码。同样,Sodium_Compat已更新为最新版本,以解决引导程序中的PHP 8问题。

基于上述内容,我们只能期望在接下来的几周/几个月内将发布包含外部依赖关系的大多数新版本,其中包含PHP 8.0兼容性修补程序。这也意味着,即使WP已进入WP 5.6的Beta阶段,一旦新版本可用,WordPress仍将需要更新这些外部依赖项。

外部依赖性(未维护)

其他外部依赖项(例如pomo,PCLZip,Snoopy,RSS)不再在外部维护,而使它们与PHP 8.0兼容的负担就由WP本身承担。

除pomo之外,这些工具中的大多数都未在WP测试套件中进行测试,甚至没有明确地从代码覆盖率和其他质量检查中排除,这使它们的PHP 8状态再次成为未知数。

另外,由于这些外部依赖项是“旧的”代码,除了基于静态分析解决选定的PHP跨版本兼容性问题外,过去几年几乎没有任何维护,因此应将此代码视为PHP 4代码,并采用谨慎和恐惧,以解决更多问题。

对更广泛的生态系统的影响

虽然上面列出的每个问题都不是什么大问题,但是在可预见的将来,所有这些问题的累加在一起却引起了人们对WordPress与PHP 8.0兼容性的严重关注。

更重要的是,WordPress从未作为独立产品运行,而是始终伴随主题和一些插件。WordPress的可扩展性是其成功的重要因素,但在兼容性方面也难以克服额外的挑战。

广泛依赖诸如apply_filters()和不受保护的全局变量之类的函数的非类型安全使用将在未来几年内令人痛苦,导致致命错误,这些错误将被误识别为属于Core或B插件,而由A插件引起这显然是引起人们关注的一个主要原因,并且很可能会吸引很多最终用户。

第3部分:yoast.com案例研究

到插件A导致插件B甚至WordPress核心崩溃的地步,我们认为最好在yoast.com上进行分析。为了更好地了解PHP 8对大型WordPress网站的影响,我们编制了一份清单,列出了目前在yoast.com上引发的所有PHP警告,这些警告在PHP 8上会导致致命错误。重要的是要注意,这仅涵盖了部分重大更改,但是它很好地表明了这些更改可能会对实时站点产生的影响。

我们做了什么?

通过用于Elastic beats的PHP_FPM模块,我们将所有PHP警告和错误直接记录到了我们的Elastic数据库中。我们决定编译最近30天发生的警告列表,并过滤掉该列表,直到可能引起PHP 8错误的警告为止。我们得到了以下列表:

错误信息 类型 计数
从空值创建默认对象 主题 266,413
ltrim()期望参数1为字符串,给定数组 可湿性粉剂 131,666
count():参数必须是实现Countable的数组或对象 插入 7,129
为foreach()提供了无效的参数 可湿性粉剂 4,685
遇到非数字值 插入 3,072
遇到非数字值 插入 3,072
遇到非数字值 插入 2,981
被零除 插入 1,288
array_filter()期望参数1为数组,给定null 插入 830
call_user_func_array()期望参数1为有效的回调,类’HelpScout_Docs_API \ Admin’没有方法’insert_post’ 可湿性粉剂 398
isset中的偏移量类型非法或为空 可湿性粉剂 267
遇到非数字值 插入 184
遇到非数字值 插入 100
count():参数必须是实现Countable的数组或对象 插入 97
count():参数必须是实现Countable的数组或对象 主题 53
遇到非数字值 现场 50
trim()期望参数1为字符串,给定数组 可湿性粉剂 38
count():参数必须是实现Countable的数组或对象 插入 28
遇到非数字值 插入 19
非法的字符串偏移量’full’ 现场 13
遇到非数字值 插入 9
为foreach()提供了无效的参数 插入 7
为foreach()提供了无效的参数 插入 4
非法字符串偏移量’usp’ 可湿性粉剂 3
count():参数必须是实现Countable的数组或对象 插入 2
为foreach()提供了无效的参数 插入 2
strlen()期望参数1为字符串,给定数组 插入 2
date()期望参数2为int,给出字符串 现场 2
date()期望参数2为int,给出字符串 现场 2
遇到非数字值 插入 1个
array_keys()期望参数1为数组,给定null 插入 1个
为foreach()提供了无效的参数 插入 1个

分析

如您所见,我们获得了一个列表,范围从在主题中每月发生250.000次以上的警告到仅两次(在PayPal结帐期间)发生的警告。在PHP 8上,总共有将近50万种32种不同类型的警告会导致致命错误。

来自任何地方的警告

在这32种警告中,我们发现6种发生在特定于yoast.com的代码中,因此不会在任何其他网站上发生。8种不同的插件直接产生了20种不同的类型。其余6个发生在WordPress核心代码中,尽管它们全部是由于与插件的交互作用所致。插件示例WordPress中断核心检出。

从频繁到稀有

在所有这些警告中,有两个非常频繁地发生,在上个月超过100.000次。一种源于我们自己的主题,另一种源于esc_url()函数,该函数使用数组而不是字符串进行调用。当我们开始在PHP 8上测试yoast.com时,这些内容很容易找到并且也很容易修复。无需花费很多精力即可复制这些内容。在暂存站点上进行简单的冒烟测试会发现这些错误。

六种类型的发生频率较低,每月发生次数超过1.000次,但少于10.000次。所有这些源于插件,我们的服务器每月处理800万个请求,因此,每1000个请求中只有不到1个发生。这些错误在暂存环境的快速测试中可能不会全部找到,但需要可靠地捕获更大量的测试。考虑到它们的出现频率,很可能会在大多数站点上造成中断,例如,如果发生在AJAX请求期间,则可能很难检测到。特别是在电子商务网站上,用户可能不会报告这些错误,而只是放弃购物车。

其他五种类型发生100到1.000次。所有这些要么来自插件,要么来自插件与WordPress核心的交互方式。考虑到它们的出现频率,在PHP 8上测试站点时不太可能会找到所有这些标记。它们非常频繁,最终用户可能会报告问题,但是由于其稀有性,这可能需要一些时间。与上述问题类似,如果这些问题发生在隐藏请求中,则可能很难检测到。

最终,我们有19种警告类型,每月发生的次数少于100次。其中五个特定于yoast.com,其余14个源自插件或它们与WordPress核心的交互。即使进行了严格的测试,也完全没有可能找到这些。这些警告发生在100.000个请求中的少于1个中,其中10个甚至少于1百万个请求中的1个。尽管应该注意的是,其中有4种是在结帐时发生的,但这些干扰可能造成的干扰可能很小,因此会更严重地影响一些特定用户。这些错误由于其稀有性可能会在几个月后才被发现。

总而言之,将WordPress网站迁移到PHP 8.0时,很容易会迅速发现少量非常频繁发生的问题,而修复这些问题将占总错误的95%。通过更严格的测试,应该很有可能占所有错误的99%,但是根据yoast.com上的数据,到现在为止,可能只发现了不到一半的不同原因。剩下的就是很少发生的问题,但是可能会在某些非常不幸的地方发生,例如我们的结帐。 

大型WordPress网站上的PHP 8兼容性看起来有些棘手

通过仅调查PHP 8中一些重大更改的子集,我们已经可以确认,这很可能会导致那些破损来源不明确的站点发生重大破损。通常,错误会在一个地方发生,但是由插件或主题在另一个地方引起的,因此这些问题很难调试。

Yoast.com显然是一个积极维护的WordPress网站,由一群专业开发人员支持。绝大多数WordPress网站都没有这种奢侈,减轻这些网站的兼容性问题也不容易。 

结论

PHP 8将包含许多重大更改。我们在本报告中描述了这些更改的一部分。我们假设的内容将对WordPress和更广泛的WordPress生态系统产生最大的影响。这些主要与警告变成错误以及许多与严格类型相关的错误有关。这些更改中的很大一部分只能在运行时检测到。

解决这些兼容性问题是一项主要任务,为此,您需要使用从静态分析到自动化测试的各种策略。要正确执行此操作,需要正确的工具。对于需要支持一系列PHP版本的WordPress之类的项目,在分析不同版本的分析工具时引入了许多额外的复杂性。这变得越来越困难,部分原因是PHP 5.x和8.x之间的语法和运行时差异是如此之大。这并不是要争论支持早于PHP的PHP版本是否是一个好主意,这里唯一的结论就是这样做越来越困难。

我们还研究了覆盖率和WordPress的PHP依赖关系的问题。为了可靠地检测兼容性,必须具有较高的测试覆盖率,而对于PHP 8而言,这尤其重要,因为PHP 8的兼容性问题比平时要多得多,其中很多问题只能在运行时才能检测到。从这个意义上讲,很难说出WordPress核心与PHP 8的真正兼容性是什么,因为测试覆盖率很低并且几乎没有依赖关系。

由于PHP 8非常重视严格的类型输入,因此WordPress的类型不安全可扩展性系统也特别容易出错,从而可能导致插件引起其他插件或WordPress本身的类型错误。为了对此进行测试,我们对上个月来自yoast.com的错误数据进行了分析。作为拥有商店的大型站点,我们认为它可以很好地表明我们可以预期的问题类型。实际上,我们发现了许多警告,这些警告将变成PHP 8的错误。

我们最后要说明的一点是,WordPress不是现有的唯一旧代码库,也不是唯一旨在支持各种PHP版本的项目。因此,本文中的信息可能也适用于其他项目。

我们将不包括任何进一步的号召性用语。这篇文章的主要目的是告知和创建与WordPress中的PHP 8兼容性相关的问题和挑战的概述。我们衷心希望它能很好地达到这一目的。 

相关文章