PHP 8.2建立在PHP 8.0和PHP 8.1的更新基礎之上。計劃於2022年11月24日釋出。
本文將詳細介紹PHP 8.2中的新功能 — 從其新功能和改進到棄用和細微更改,我們將一一介紹。
隨著PHP 8.2於2022年7月19日進入其功能凍結狀態,您可以預期此列表不會有重大新增。
PHP 8.2的新特性和改進
讓我們從探索所有最新的PHP 8.2功能開始。這是一個相當廣泛的列表:
- 新的只讀類
- 允許true、false和null作為獨立型別
- 析取正規化 (DNF) 型別
- 編輯回溯中的敏感引數
- 新的mysqli_execute_query函式和mysqli::execute_query方法
- 在const表示式中獲取列舉屬性
- 允許特徵中的常量
- 棄用動態屬性(和新的#[AllowDynamicProperties]屬性)
- 棄用部分支援的可呼叫物件
- 棄用#utf8_encode()和utf8_decode()函式
- 棄用${}字串插值
- 棄用Base64/QPrint/Uuencode/HTML實體的mbstring函式
- 從mysqli中刪除對libmysql的支援
- 與語言環境無關的大小寫轉換
- 隨機擴充套件改進
新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 }
您還可以將抽象類或最終類宣告為readonly
. 在這裡,關鍵字的順序無關緊要。
abstract readonly class Free {} final readonly class Dom {}
你也可以宣告一個沒有屬性的readonly
類。實際上,這可以防止動態屬性,同時仍允許子類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 ...
允許 true
、 false
和 null
作為獨立型別
PHP已經包含標量型別,如int
,string
和bool
. 這在PHP 8.0中通過新增union types進行了擴充套件,允許值具有不同的型別。同一個RFC也允許使用 false
and null
作為聯合型別的一部分——儘管它們不允許作為獨立型別。
如果您嘗試將false
or宣告null
為獨立型別(而不將它們作為聯合型別的一部分),則會導致致命錯誤。
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新增了對使用false
和null
作為獨立型別的支援。通過這個新增,PHP的型別系統更具表現力和完整。您現在可以精確地宣告返回、引數和屬性型別。
此外,PHP仍然不包含true
型別,這似乎是false
型別的自然對應物。PHP 8.2修復了這個問題並新增了對true
型別的支援。它不允許強制,就像false
型別的行為一樣。
true
和false
型別本質上都是PHP型別的聯合型別bool
。為避免冗餘,您不能在聯合型別中同時宣告這三種型別。這樣做會導致編譯時致命錯誤。
析取正規化 (DNF) 型別
析取正規化 (DNF)是一種組織布林表示式的標準化方法。它由連詞的析取組成——用布林術語來說,這是OR of ANDs。
將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)
是無效的,因為兩個ORed段在邏輯上是相同的。
除此之外,也不允許作為另一個段的嚴格子集的段。這是因為超集已經擁有子集的所有例項,因此使用DNF是多餘的。
編輯回溯中的敏感引數
與幾乎所有程式語言一樣,PHP允許在程式碼執行的任何時候跟蹤其呼叫堆疊。堆疊跟蹤使除錯程式碼以修復錯誤和效能瓶頸變得容易。
使用工具跟蹤緩慢的WooCommerce事務
執行堆疊跟蹤不會停止程式的執行。通常,大多數堆疊跟蹤在後臺執行並以靜默方式記錄下來——如果需要,供以後檢查。
但是,如果您與第三方服務共享這些詳細的PHP堆疊跟蹤中的一些可能是一個缺點——通常用於錯誤日誌分析、錯誤跟蹤等。這些堆疊跟蹤可能包含敏感資訊,例如使用者名稱、密碼和環境變數.
這個RFC提案給出了一個這樣的例子:
一個常見的“違規者”是PDO,它將資料庫密碼作為建構函式引數並立即嘗試在建構函式中連線到資料庫,而不是使用純建構函式和separate ->connect()方法。因此,當資料庫連線失敗時,堆疊跟蹤將包括資料庫密碼:
PDOException: SQLSTATE[HY000] [2002] No such file or directory in /var/www/html/test.php:3Stack trace: #0 /var/www/html/test.php(3): PDO->__construct('mysql:host=loca...', 'root', 'password')#1 {main}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}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); }
獲取表示式enum
中的屬性const
RFC建議允許->/?->
操作員獲取const
表示式中的enum
屬性。
這個新特性的主要原因是你不能在某些地方使用enum
物件,比如陣列鍵。在這種情況下,您必須重複enum
例項的值才能使用它。
允許在不允許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]屬性)
- 棄用部分支援的可呼叫物件
- 棄用#utf8_encode()和utf8_decode()函式
- 棄用${}字串插值
- 棄用Base64/QPrint/Uuencode/HTML例項mbstring函式
- 從mysqli中刪除對libmysql的支援
- 與語言環境無關的大小寫轉換
- 隨機擴充套件改進
棄用動態屬性(和新#[AllowDynamicProperties]
屬性)
在PHP 8.1之前,您可以在PHP中動態設定和檢索未宣告的類屬性。例如:
class Post { private int $pid; } $post = new Post(); $post->name = 'Wbolt';
在這裡, 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
. 此外,任何通過__set()
或者__get()
PHP魔術方法訪問的屬性都不會被視為動態屬性,因此它們不會發出棄用通知。
棄用部分支援的可呼叫物件
PHP 8.2的另一項更改(儘管影響更小)是棄用部分支援的callables。
這些可呼叫物件被稱為部分支援,因為您無法通過$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開始,任何呼叫此類可呼叫物件的嘗試(例如viacall_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支援的擴充套件,例如mbstring
、 iconv
和 intl
。
棄用${}
字串插值
PHP允許通過以下幾種方式在帶有雙引號 ( "
) 和heredoc ( <<<
) 的字串中嵌入變數:
- 直接嵌入變數——
“$foo”
- 變數外有大括號——
“{$foo}”
- 美元符號後有大括號 –
“${foo}”
- 變數變數
“${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的別名)
- Quoted-Printable
- qprint(Quoted-Printable的別名)
從PHP 8.2開始,使用mbstring對上述任何內容進行編碼/解碼都會發出棄用通知。PHP 9.0將完全移除對這些編碼的mbstring支援。
PHP 8.2中的其他小改動
最後,我們可以討論PHP 8.2的微小變化,包括它刪除的特性和功能。
從mysqli中刪除對libmysql的支援
截至目前,PHP允許mysqli
和PDO_mysql
驅動程式針對mysqlnd
和libmysql
庫進行構建。但是,自PHP 5.4起預設和推薦的驅動程式是mysqlnd
.
這兩種驅動程式都有許多優點和缺點。然而,移除對其中之一的支援——理想情況下,移除libmysql
它不是預設的——將簡化PHP的程式碼和單元測試。
為了證明這一點,RFC列出了許多優點mysqlnd
:
- 它與PHP捆綁在一起
- 它使用PHP記憶體管理來監控記憶體使用情況並提高效能
- 提供quality-of-life函式(例如
get_result()
) - 使用PHP原生型別返回數值
- 它的功能不依賴於外部庫
- 可選外掛功能
- 支援非同步查詢
RFC還列出了libmysql
的一些優點,包括:
- 自動重新連線是可能的(
mysqlnd
故意不支援此功能,因為它很容易被利用) - LDAP和SASL身份驗證模式(
mysqlnd
也可能很快新增此功能)
此外,RFC列出了許多libmysql
缺點——與PHP記憶體模型不相容、許多失敗的測試、記憶體洩漏、版本之間的不同功能等。
記住所有這些,PHP8.2取消了對基於libmysql
構建mysqli
的支援。
如果您想新增任何僅適用於libmysql
的功能,則必須將其顯式新增mysqlnd
為功能請求。此外,您不能新增自動重新連線。
與語言環境無關的大小寫轉換
在PHP 8.0之前,PHP的語言環境是從系統環境繼承而來的。但這在某些極端情況下可能會導致問題。
在安裝Linux時設定您的語言將為它的內建命令設定適當的使用者介面語言。但是,它也意外地改變了C庫的字串處理功能的工作方式。
例如,如果您在安裝Linux時選擇了“土耳其語”或“哈薩克語”語言,您會發現呼叫toupper('i')
以獲取其大寫等效項將獲得點大寫字母I (U+0130, İ
)。
PHP8.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還包括許多新功能和細微更改。我們將在下面提到它們,並附有指向其他資源的連結:
curl_upkeep
新函式:PHP 8.2將這個新函式新增到其Curl擴充套件中。它呼叫libcurl中的curl_easy_upkeep()
函式,libcurl是PHP Curl擴充套件使用的底層C庫。ini_parse_quantity
新函式:PHP INI指令接受帶有乘數字尾的資料大小。例如,您可以將25MB 寫為25M
,或將42GB寫為42G
. 這些字尾在PHP INI檔案中很常見,但在其他地方並不常見。這個新函式解析PHP INI值並以位元組為單位返回它們的資料大小。memory_reset_peak_usage
新函式:此函式重置該功能返回的峰值記憶體使用量memory_get_peak_usage
。當您多次執行相同的操作並想要記錄每次執行的峰值記憶體使用量時,它會很方便。- 支援函式中的無捕獲修飾符 (
/n
)preg_*
:在正規表示式中,()
元字元表示捕獲組。這意味著返回括號內表示式的所有匹配項。PHP 8.2新增了一個非捕獲修飾符 (/n
) 來阻止這種行為。 - 使
iterator_*()
family接受所有可迭代物件:截至目前,PHPiterator_*()
family只接受\Traversables
(即不允許使用普通陣列)。這是不必要的限制,這個RFC解決了這個問題。
小結
PHP 8.2建立在PHP 8.0和PHP 8.1的巨大改進之上,這絕非易事。我們認為PHP 8.2最令人興奮的特性是其新的獨立型別、只讀屬性和眾多效能改進。
我們迫不及待地想用各種PHP框架和CMS對PHP 8.2進行基準測試。
評論留言