PHP 8.2 的新特性——新特性、棄用、變更等

已發表: 2022-08-09

PHP 8.2 建立在 PHP 8.0 和 PHP 8.1 的更新基礎之上。 計劃於 2002 年 11 月 24 日發布。

本文將詳細介紹 PHP 8.2 中的新特性——從新特性和改進到棄用和細微更改,我們將一一介紹。

隨著 PHP 8.2 於 2022 年 7 月 19 日進入其功能凍結狀態,您可以預期此列表不會有重大添加。

興奮的? 我們也是。

讓我們開始!

PHP 8.2 的新特性和改進

讓我們從探索所有最新的 PHP 8.2 功能開始。 這是一個相當廣泛的列表:

新的readonly

PHP 8.1 引入了類屬性的readonly特性。 現在,PHP 8.2 增加了將整個類聲明為readonly的支持。

如果你將一個類聲明為readonly ,它的所有屬性都會自動繼承readonly特性。 因此,將類聲明為readonly readonly

例如,在 PHP 8.1 中,您必須編寫這段乏味的代碼來將所有類屬性聲明為readonly

 class MyClass { public readonly string $myValue, public readonly int $myOtherValue public readonly string $myAnotherValue public readonly int $myYetAnotherValue }

想像一下還有更多的屬性。 現在,使用 PHP 8.2,你可以這樣寫:

 readonly class MyClass { public string $myValue, public int $myOtherValue public string $myAnotherValue public int $myYetAnotherValue }

您還可以將 abstract 或 final 類聲明為readonly 。 在這裡,關鍵字的順序無關緊要。

 abstract readonly class Free {} final readonly class Dom {}

您還可以聲明一個沒有屬性的readonly類。 實際上,這可以防止動態屬性,同時仍允許子類顯式聲明其readonly屬性。

接下來, readonly類只能包含類型化的屬性——聲明單個只讀屬性的規則相同。

如果您不能聲明嚴格類型的屬性,則可以使用mixed類型屬性。

試圖聲明一個沒有類型屬性的readonly類將導致致命錯誤:

 readonly class Type { public $nope; } Fatal error: Readonly property Type::$nope must have type in ... on line ...

此外,您不能為某些 PHP 功能聲明readonly

  • 枚舉(因為它們不能包含任何屬性)
  • 性狀
  • 接口

嘗試將這些功能中的任何一個聲明為readonly將導致 Parse 錯誤。

 readonly interface Destiny {} Parse error: syntax error, unexpected token "interface", expecting "abstract" or "final" or "readonly" or "class" in ... on line ...

與所有 PHP 關鍵字一樣, readonly關鍵字不區分大小寫。

PHP 8.2 還棄用了動態屬性(稍後會詳細介紹)。 但是您不能阻止將動態屬性添加到類中。 但是,對readonly類這樣做只會導致致命錯誤。

 Fatal error: Readonly property Test::$test must have type in ... on line ...

允許truefalsenull作為獨立類型

PHP 已經包含標量類型,如intstringbool 。 這在 PHP 8.0 中通過添加聯合類型進行了擴展,允許值具有不同的類型。 同一個 RFC 還允許使用falsenull作為聯合類型的一部分——儘管它們不允許作為獨立類型。

如果您嘗試將falsenull或作為獨立類型聲明——它們不是聯合類型的一部分——則會導致致命錯誤。

 function spam(): null {} function eggs(): false {} Fatal error: Null can not be used as a standalone type in ... on line ... Fatal error: False can not be used as a standalone type in ... on line ...

為了避免這種情況,PHP 8.2 添加了對使用falsenull作為獨立類型的支持。 通過這個添加,PHP 的類型系統更具表現力和完整。 您現在可以精確地聲明返回、參數和屬性類型。

此外,PHP 仍然不包含true類型,這似乎是false類型的自然對應物。 PHP 8.2 修復了這個問題並添加了對true類型的支持。 它不允許強制,就像false類型的行為一樣。

truefalse類型本質上都是 PHP 的bool類型的聯合類型。 為避免冗餘,您不能在聯合類型中同時聲明這三種類型。 這樣做會導致編譯時致命錯誤。

析取範式 (DNF) 類型

析取範式 (DNF) 是一種組織布爾表達式的標準化方法。 它由連詞的析取組成——用布爾術語來說,這是 AND 的 OR

將 DNF 應用於類型聲明允許以標準方式編寫解析器可以處理的聯合和交集類型。 如果使用得當,PHP 8.2 的新 DNF 類型功能既簡單又強大。

RFC 給出了以下示例。 它假定以下接口和類定義已經存在:

 interface A {} interface B {} interface C extends A {} interface D {} class W implements A {} class X implements B {} class Y implements A, B {} class Z extends Y implements C {}

使用 DNF 類型,您可以對屬性、參數和返回值執行類型聲明,如下所示:

 // Accepts an object that implements both A and B, // OR an object that implements D (A&B)|D // Accepts an object that implements C, // OR a child of X that also implements D, // OR null C|(X&D)|null // Accepts an object that implements all three of A, B, and D, // OR an int, // OR null. (A&B&D)|int|null

在某些情況下,屬性可能不是 DNF 形式。 這樣聲明它們將導致解析錯誤。 但是你總是可以將它們重寫為:

 A&(B|D) // Can be rewritten as (A&B)|(A&D) A|(B&(D|W)|null) // Can be rewritten as A|(B&D)|(B&W)|null

您應該注意,DNF 類型的每個段都必須是唯一的。 例如,聲明(A&B)|(B&A)是無效的,因為兩個OR ed 段在邏輯上是相同的。

除此之外,也不允許作為另一個段的嚴格子集的段。 這是因為超集已經擁有子集的所有實例,因此使用 DNF 是多餘的。

編輯回溯中的敏感參數

與幾乎所有編程語言一樣,PHP 允許在代碼執行的任何時候跟踪其調用堆棧。 堆棧跟踪使調試代碼以修復錯誤和性能瓶頸變得容易。 它構成了 Kinsta APM 等工具的支柱,這是我們為 WordPress 網站定制設計的性能監控工具。

通過 Kinsta APM 工具跟踪緩慢的 WooCommerce 交易。
使用 Kinsta APM 跟踪緩慢的 WooCommerce 交易。

執行堆棧跟踪不會停止程序的執行。 通常,大多數堆棧跟踪在後台運行並以靜默方式記錄下來——如果需要,供以後檢查。

但是,如果您與第三方服務共享這些詳細的 PHP 堆棧跟踪中的一些可能是一個缺點——通常用於錯誤日誌分析、錯誤跟踪等。這些堆棧跟踪可能包含敏感信息,例如用戶名、密碼和環境變量.

這個 RFC 提案給出了一個這樣的例子:

一個常見的“違規者”是 PDO,它將數據庫密碼作為構造函數參數並立即嘗試在構造函數中連接到數據庫,而不是使用純構造函數和單獨的 ->connect()方法。 因此,當數據庫連接失敗時,堆棧跟踪將包括數據庫密碼:

 PDOException: SQLSTATE[HY000] [2002] No such file or directory in /var/www/html/test.php:3 Stack trace: #0 /var/www/html/test.php(3): PDO->__construct('mysql:host=loca...', 'root', 'password') #1 {main}

PHP 8.2 允許您使用新的\SensitiveParameter屬性標記此類敏感參數。 任何標記為敏感的參數都不會在您的回溯中列出。 因此,您可以與任何第三方服務共享它們而無需擔心。

這是一個帶有單個敏感參數的簡單示例:

 <?php function example( $ham, #[\SensitiveParameter] $eggs, $butter ) { throw new \Exception('Error'); } example('ham', 'eggs', 'butter'); /* Fatal error: Uncaught Exception: Error in test.php:8 Stack trace: #0 test.php(11): test('ham', Object(SensitiveParameterValue), 'butter') #1 {main} thrown in test.php on line 8 */

當您生成回溯時,任何具有\SensitiveParameter屬性的參數都將被替換為\SensitiveParameterValue對象,並且它的實際值永遠不會存儲在跟踪中。 SensitiveParameterValue對象封裝了實際的參數值——如果您出於任何原因需要它。

新的mysqli_execute_query函數和mysqli::execute_query方法

您是否曾經使用過帶有危險轉義用戶值的mysqli_query()函數來運行參數化 MySQLi 查詢?

PHP 8.2 使用新的mysqli_execute_query($sql, $params)函數和mysqli::execute_query方法使運行參數化 MySQLi 查詢更容易。

本質上,這個新函數是mysqli_prepare()mysqli_execute()mysqli_stmt_get_result()函數的組合。 有了它,MySQLi 查詢將準備好、綁定(如果您傳遞任何參數),並在函數本身內執行。 如果查詢成功運行,它將返回一個mysqli_result對象。 如果不成功,它將返回false

RFC 提案提供了一個簡單但功能強大的示例:

 foreach ($db->execute_query('SELECT * FROM user WHERE name LIKE ? AND type_id IN (?, ?)', [$name, $type1, $type2]) as $row) { print_r($row); }

const表達式中獲取enum屬性

該 RFC 建議允許->/?->運算符獲取const表達式中的enum屬性。

這個新特性的主要原因是你不能在某些地方使用enum對象,比如數組鍵。 在這種情況下,您必須重複enum case 的值才能使用它。

允許在不允許enum對象的地方獲取enum屬性可以簡化此過程。

這意味著以下代碼現在有效:

 const C = [self::B->value => self::B];

為了安全起見,這個 RFC 還包括對 nullsafe 運算符?->的支持。

允許特徵中的常量

PHP 包含一種重用代碼的方法,稱為 Traits。 它們非常適合跨類重用代碼。

目前,Traits 只允許定義方法和屬性,但不允許定義常量。 這意味著您不能在 Trait 本身內定義 Trait 所期望的不變量。 要繞過這個限制,您需要在其組合類或由其組合類實現的接口中定義常量。

該 RFC 提議允許在 Traits 中定義常量。 可以像定義類常量一樣定義這些常量。 這個直接取自 RFC 的示例清楚地說明了它的用法:

 trait Foo { public const FLAG_1 = 1; protected const FLAG_2 = 2; private const FLAG_3 = 2; public function doFoo(int $flags): void { if ($flags & self::FLAG_1) { echo 'Got flag 1'; } if ($flags & self::FLAG_2) { echo 'Got flag 2'; } if ($flags & self::FLAG_3) { echo 'Got flag 3'; } } }

Trait 常量也被合併到組合類的定義中,與 Trait 的屬性和方法定義相同。 它們也具有與 Traits 的屬性類似的限制。 正如 RFC 中所指出的,這個提議——雖然是一個好的開始——需要進一步的工作來充實這個特性。

PHP 8.2 中的棄用

我們現在可以開始探索 PHP 8.2 中的所有棄用。 這個列表並不像它的新功能那麼大:

棄用動態屬性(和新的#[AllowDynamicProperties]屬性)

在 PHP 8.1 之前,您可以在 PHP 中動態設置和檢索未聲明的類屬性。 例如:

 class Post { private int $pid; } $post = new Post(); $post->name = 'Kinsta';

在這裡, Post類沒有聲明name屬性。 但是因為 PHP 允許動態屬性,你可以在類聲明之外設置它。 這是它最大的——也可能是唯一的——優勢。

動態屬性允許在您的代碼中出現意外的錯誤和行為。 例如,如果在類之外聲明類屬性時犯了任何錯誤,很容易忘記它——尤其是在調試該類中的任何錯誤時。

從 PHP 8.2 開始,不推薦使用動態屬性。 將值設置為未聲明的類屬性將在第一次設置該屬性時發出棄用通知。

 class Foo {} $foo = new Foo; // Deprecated: Creation of dynamic property Foo::$bar is deprecated $foo->bar = 1; // No deprecation warning: Dynamic property already exists. $foo->bar = 2;

但是,從 PHP 9.0 開始,設置相同會引發ErrorException錯誤。

如果你的代碼充滿了動態屬性——並且有很多 PHP 代碼——並且如果你想在升級到 PHP 8.2 後停止這些棄用通知,你可以使用 PHP 8.2 的新#[AllowDynamicProperties]屬性來允許動態類的屬性。

 #[AllowDynamicProperties] class Pets {} class Cats extends Pets {} // You'll get no deprecation warning $obj = new Pets; $obj->test = 1; // You'll get no deprecation warning for child classes $obj = new Cats; $obj->test = 1;

根據 RFC,標記為#[AllowDynamicProperties]的類及其子類可以繼續使用動態屬性而無需棄用或刪除。

您還應該注意,在 PHP 8.2 中,唯一標記為#[AllowDynamicProperties]的捆綁類是stdClass 。 此外,通過__get()__set() PHP 魔術方法訪問的任何屬性都不被視為動態屬性,因此它們不會引發棄用通知。

棄用部分支持的 Callables

PHP 8.2 的另一項更改(儘管影響更小)是棄用部分支持的可調用對象。

這些可調用對像被稱為部分支持,因為您無法通過$callable()直接與它們交互。 您只能使用call_user_func($callable)函數來訪問它們。 此類可調用對象的列表並不長:

 "self::method" "parent::method" "static::method" ["self", "method"] ["parent", "method"] ["static", "method"] ["Foo", "Bar::method"] [new Foo, "Bar::method"]

從 PHP 8.2 開始,任何調用此類可調用對象的嘗試(例如通過call_user_func()array_map()函數)都會引發棄用警告。

原始 RFC 給出了這種棄用的可靠理由:

除了最後兩種情況,所有這些可調用對像都是上下文相關的。 "self::method"所指的方法取決於從哪個類執行調用或可調用性檢查。 在實踐中,當以[new Foo, "parent::method"]的形式使用時,這通常也適用於最後兩種情況。

減少可調用對象的上下文相關性是本 RFC 的次要目標。 在這個 RFC 之後,唯一剩下的範圍依賴是方法可見性: "Foo::bar"可能在一個範圍內可見,但在另一個範圍內不可見。 如果將來可調用對象僅限於公共方法(而私有方法必須使用一流的可調用對像或Closure::fromCallable()以使其與範圍無關),那麼可調用類型將變得明確定義並且可以用作屬性類型。 但是,對可見性處理的更改不建議作為本 RFC 的一部分

根據原始 RFC, is_callable()函數和callable類型將繼續接受這些可調用對像作為異常。 但直到從 PHP 9.0 開始完全刪除對它們的支持。

為避免混淆,此棄用通知範圍通過新的 RFC 進行了擴展——它現在包括這些例外。

很高興看到 PHP 朝著定義良好的callable類型發展。

棄用#utf8_encode()utf8_decode()函數

PHP 的內置函數utf8_encode()utf8_decode()將 ISO-8859-1(“Latin 1”)編碼的字符串與 UTF-8 轉換。

但是,它們的名稱暗示了比其實現允許的更普遍的用途。 “Latin 1”編碼通常與“Windows Code Page 1252”等其他編碼混淆。

此外,當這些函數無法正確轉換任何字符串時,您通常會看到 Mojibake。 缺少錯誤消息也意味著很難發現它們,尤其是在一大堆清晰的文本中。

PHP 8.2 棄用了#utf8_encode()utf8_decode()函數。 如果您調用它們,您將看到這些棄用通知:

 Deprecated: Function utf8_encode() is deprecated Deprecated: Function utf8_decode() is deprecated

RFC 建議使用 PHP 支持的擴展,例如mbstringiconvintl

棄用${}字符串插值

PHP 允許通過以下幾種方式將變量嵌入帶有雙引號 ( " ) 和 heredoc ( <<< ) 的字符串中:

  1. 直接嵌入變量—— “$foo”
  2. 變量外有大括號—— “{$foo}”
  3. 美元符號後有大括號—— “${foo}”
  4. 可變變量—— “${expr}” ——相當於使用(string) ${expr}

前兩種方式各有利弊,而後兩種語法複雜且相互衝突。 PHP 8.2 棄用了最後兩種字符串插值方法。

您應該避免以這種方式插入字符串:

 "Hello, ${world}!"; Deprecated: Using ${} in strings is deprecated "Hello, ${(world)}!"; Deprecated: Using ${} (variable variables) in strings is deprecated

從 PHP 9.0 開始,這些棄用將升級為拋出異常錯誤。

棄用 Base64/QPrint/Uuencode/HTML 實體的 mbstring 函數

PHP 的 mbstring(多字節字符串)函數幫助我們處理 Unicode、HTML 實體和其他傳統文本編碼。

但是,Base64、Uuencode 和 QPrint 不是文本編碼,它們仍然是這些函數的一部分——主要是由於遺留原因。 PHP 還包括這些編碼的單獨實現。

至於 HTML 實體,PHP 有內置函數htmlspecialchars()htmlentities()來更好地處理這些。 例如,與 mbstring 不同,這些函數也將轉換<>&字符到 HTML 實體。

此外,PHP 一直在改進其內置功能——就像 PHP 8.1 中的 HTML 編碼和解碼功能一樣。

因此,請記住所有這些,PHP 8.2 不贊成使用 mbstring 進行這些編碼(標籤不區分大小寫):

  • BASE64
  • UUENCODE
  • HTML實體
  • html(HTML-ENTITIES 的別名)
  • 引用-可打印
  • qprint(Quoted-Printable 的別名)

從 PHP 8.2 開始,使用 mbstring 對上述任何內容進行編碼/解碼都會發出棄用通知。 PHP 9.0 將完全移除對這些編碼的 mbstring 支持。

PHP 8.2 中的其他小改動

最後,我們可以討論 PHP 8.2 的微小變化,包括它刪除的特性和功能。

從 mysqli 中刪除對 libmysql 的支持

截至目前,PHP 允許mysqliPDO_mysql驅動程序針對mysqlndlibmysql庫進行構建。 但是,自 PHP 5.4 起,默認和推薦的驅動程序是mysqlnd

這兩種驅動程序都有許多優點和缺點。 然而,刪除對其中之一的支持——理想情況下,刪除libmysql ,因為它不是默認的——將簡化 PHP 的代碼和單元測試。

為了證明這一點,RFC 列出了mysqlnd的許多優點:

  • 它與 PHP 捆綁在一起
  • 它使用 PHP 內存管理來監控內存使用情況和
    提高性能
  • 提供生活質量功能(例如get_result()
  • 使用 PHP 原生類型返回數值
  • 它的功能不依賴於外部庫
  • 可選插件功能
  • 支持異步查詢

RFC 還列出了libmysql的一些優點,包括:

  • 自動重新連接是可能的( mysqlnd故意不支持此功能,因為它很容易被利用)
  • LDAP 和 SASL 身份驗證模式( mysqlnd也可能很快添加此功能)

此外,RFC 列出了libmysql的許多缺點——與 PHP 內存模型不兼容、許多失敗的測試、內存洩漏、版本之間的不同功能等。

牢記這一切,PHP 8.2 刪除了對針對libmysql構建mysqli的支持。

如果您想添加任何僅可用於libmysql的功能,您必須將其顯式添加到mysqlnd作為功能請求。 此外,您不能添加自動重新連接。

與語言環境無關的大小寫轉換

在 PHP 8.0 之前,PHP 的語言環境是從系統環境繼承而來的。 但這在某些極端情況下可能會導致問題。

在安裝 Linux 時設置語言將為它的內置命令設置適當的用戶界面語言。 但是,它也意外地改變了 C 庫的字符串處理功能的工作方式。

例如,如果您在安裝 Linux 時選擇了“土耳其語”或“哈薩克語”語言,您會發現調用toupper('i')以獲取其大寫等效項將獲得點大寫 I (U+0130, I )。

PHP 8.0 通過將默認語言環境設置為“C”來阻止這種異常,除非用戶通過setlocale()顯式更改它。

PHP 8.2 更進一步,從大小寫轉換中移除了區域設置敏感性。 該 RFC 主要更改了strtolower()strtoupper()和相關函數。 閱讀 RFC 以獲取所有受影響函數的列表。

作為替代方案,如果您想使用本地化大小寫轉換,則可以使用mb_strtolower()

隨機擴展改進

PHP 正計劃徹底檢查其隨機功能。

到目前為止,PHP 的隨機功能嚴重依賴於 Mersenne Twister 狀態。 然而,這個狀態隱式地存儲在 PHP 的全局區域中——用戶無法訪問它。 在初始播種階段和預期用途之間添加隨機化函數會破壞代碼。

當您的代碼使用外部包時,維護此類代碼可能會更加複雜。

因此,PHP 當前的隨機功能無法一致地再現隨機值。 它甚至無法通過統一隨機數生成器的經驗統計測試,例如 TestU01 的 Crush 和 BigCrush。 Mersenne Twister 的 32 位限制進一步加劇了這種情況。

因此,如果您需要加密安全的隨機數,不建議使用 PHP 的內置函數—— shuffle()str_shuffle()array_rand() 。 在這種情況下,您需要使用random_int()或類似函數來實現一個新函數。

然而,在投票開始後,該 RFC 出現了幾個問題。 這一挫折迫使 PHP 團隊在單獨的 RFC 中記錄所有問題,並為每個問題創建一個投票選項。 他們只有在達成共識後才會決定繼續前進。

PHP 8.2 中的其他 RFC

PHP 8.2 還包括許多新功能和細微更改。 我們將在下面提到它們,並附有指向其他資源的鏈接:

  1. curl_upkeep函數:PHP 8.2 將這個新函數添加到其 Curl 擴展中。 它調用 libcurl 中的curl_easy_upkeep()函數,這是 PHP Curl 擴展使用的底層 C 庫。
  2. 新的ini_parse_quantity函數:PHP INI 指令接受帶有乘數後綴的數據大小。 例如,您可以將 25 MB 寫為25M ,或將 42 GB 寫為42G 。 這些後綴在 PHP INI 文件中很常見,但在其他地方並不常見。 這個新函數解析 PHP INI 值並以字節為單位返回它們的數據大小。
  3. 新的memory_reset_peak_usage函數:此函數重置memory_get_peak_usage函數返回的峰值內存使用量。 當您多次運行相同的操作並想要記錄每次運行的峰值內存使用量時,它會很方便。
  4. preg_*函數中支持無捕獲修飾符 ( /n ):在正則表達式中, ()元字符表示捕獲組。 這意味著返回括號內表達式的所有匹配項。 PHP 8.2 添加了一個不捕獲修飾符 ( /n ) 來阻止這種行為。
  5. 使iterator_*()系列接受所有可迭代對象:到目前為止,PHP 的iterator_*()系列只接受\Traversables (即不允許使用普通數組)。 這是不必要的限制,這個 RFC 解決了這個問題。

概括

PHP 8.2 建立在 PHP 8.0 和 PHP 8.1 的巨大改進之上,這絕非易事。 我們認為 PHP 8.2 最令人興奮的特性是其新的獨立類型、只讀屬性和眾多性能改進。

我們迫不及待地想用各種 PHP 框架和 CMS 對 PHP 8.2 進行基準測試。

請務必將此博客文章添加為書籤,以供您將來參考。

您最喜歡 PHP 8.2 的哪些特性? 您最不喜歡哪些棄用? 請在評論中與我們的社區分享您的想法!