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兼容性相關的問題和挑戰的概述。我們衷心希望它能很好地達到這一目的。 

相關文章