PHP 8 中的新功能:您需要知道的一切!
已發表: 2021-01-06我們將在2020 年 11 月 26 日與 PHP8 取得聯繫,當然,有很多關於此版本中即將推出的功能的文章。
由於它是一個主要版本,因此會有重大變化和新功能,因此了解所有變化非常重要。 考慮 PHP8 將如何影響您的應用程序以及需要採取哪些措施來確保您可以方便地升級而不會發生意外,這將使您更容易思考。
我們經歷了一些最重要的更改,以確定我們將在下一個 PHP 版本中獲得什麼以及值得批評的地方。
讓我們從概述開始吧!
PHP 8 概述
如上所述,PHP 8 為該語言引入了一組新特性、改進、功能和棄用。 其中,討論最多的特性是 JIT 編譯器。 然而,諸如 JIT 之類的性能改進特性值得關注,但語法改進預計將對 PHP 從業者產生更多真正的影響,我們會說,“至少在短期內”。
變化的歷史
一些 PHP 更改被提出、辯論、實施,並在短期內得到進一步批准。 它們很受歡迎,沒有爭議,並且有一種自然的方法來實現它們。
在那之後,在我們最終接受它們之前,那些經過多次嘗試、失敗和返回的。 有幾次,實現要花很長時間來梳理,有時想法本身只是半生不熟,有時社區本身還沒有對這個想法熱身——“還不是時候”。
學分完全屬於類型類別。 它們於 2016 年首次針對 PHP 7.1 提出。 然而,他們遇到了頑固的抵抗,並以巨大的優勢輸掉了接受投票。 現在,快進四年,一個非常相似的提案,如果稍微縮小範圍,只有一個反對者通過。 這顯然是一個時機已經到來的想法!
舊代碼有什麼問題?
由於 PHP 8 是一個巨大的新版本,我們應該期望舊代碼不再兼容。 然而,大多數可能帶來複雜性的更改已經在7.2 、 7.3和7.4版本中傳達。 現在讓我們談談最後的變化。 他們是:
魔術報價遺產
- 真實的類型
- FILTER_SANITIZE_MAGIC_QUOTES過濾器
- 從非靜態閉包中解綁$this
- array_key_exists()與對象
- mb_strrpos()編碼作為第三個參數
- 反射export()方法
- convert_cyr_string()函數
- implode()參數順序混合
- restore_include_path()函數
- hebrevc()函數
- money_format()函數
- allow_url_include ini 指令
- ezmlm_hash()函數
PHP 8 中的新函數
str_contains
如果一個字符串包含另一個字符串,那麼有很多方法可以找出答案。
通常,您將使用 strpos()。 如您所知, strpos() 在您想要尋找的針旁邊放了一個乾草堆。 它返回一個整數,顯示您看到針的第一個位置。
現在,當它返回另一個字符串的位置時,您根本無法檢查 strpos() 是否發現了它; 如果它返回“0”(位置是零索引並且從 0 而不是 1 開始),那麼條件會將其視為假值,並指示它沒有找到。
這意味著什麼?
你必須寫有條件的——“strpos($haystack, $needle) !== false。” False 表示找不到字符串的位置。 這是一種在字符串中搜索字符串的不透明且深奧的方法! 好吧,不是那麼混亂。
為了避免這種情況,PHP 8 帶來了 str_contains()。 str_contains() 的工作是返回一個簡單的布爾值,顯示針是否存在於大海撈針中。 這更容易編寫,除此之外,作為維護代碼的人來理解,不是嗎?
if (str_contains( 'Foo Bar Baz' , 'Foo' )) { // FOUND }
PHP 8:引擎特性和變化
PHP 8 中有一些新的引擎特性和變化。毫無疑問,最重要的特性是新的 JIT 編譯器。
JIT 編譯器
- LSP 執行
- 不兼容的方法簽名上的致命錯誤
- 資源“類”
- XML-RPC 現在在 PECL 中
- 斷言行為
- 反射變化
出於本博客的核心目的,我們將重點關注 JIT 編譯器、資源“類”,最後是反射 API 的變化。
即時編譯器 ( RFC )
由於 PHP7 發布之前的速度改進,Just-In-Time(或 JIT)編譯器誕生了。 它被引入 PHP8 是因為在不使用 JIT 的情況下幾乎沒有更多的速度改進。 這個想法是它將提高 PHP 性能。
PHP 代碼在執行時被翻譯成字節碼,這些字節碼被進一步用於執行程序中的步驟。
PHP 將分析您執行的代碼,這就是 JIT 的含義。 此外,它可以在您執行代碼時做出實時決策,而不是對代碼進行性能改進。 它將在 CPU 密集型應用程序中高度可用,而不僅僅是在基於 Web 的場景中使用。
這反映了服務器端 PHP 應用程序肯定會在 PHP 內置 JIT 系統中更加普遍。
如果要使用它,首先需要激活 JIT。 在我們的測試系統(Ubuntu 20.04)上,我們已經安裝了 PHP opcache 模塊,我們安裝了核心 PHP8 包。 我們已經在 /etc/php/8.0/cli/conf.d/10-opcache.ini 的文件中進行了配置。
現在,你們都準備好激活 JIT 了,對吧?
- 啟用 opcache
您可以將內存存儲分配給opcache.jit_buffer_size設置。 您的文件將在我的系統上顯示為這樣。
- zend_extension=opcache.so
- opcache.enable_cli=1
- ; php opcache 模塊的配置
- opcache.jit_buffer_size=256M
此外使用 opcache_get_status() 函數來確保其是否處於活動狀態。 將您的目光移到這個數組的“jit”部分,並獲取有關 JIT 當前狀態的信息。
var_dump(opcache_get_status()['jit']);
如果 JIT 已被完美激活,這應該會打印出來,如下所示。
- 數組(7){
- [“啟用”]=>
- 布爾(真)
- [“開”]=>
- 布爾(真)
- [“善良”]=>
- 整數(5)
- [“選擇級別”]=>
- 整數(4)
- [“opt_flags”]=>
- 整數(6)
- [“緩衝區大小”]=>
- 整數(268435440)
- [“buffer_free”]=>
- 整數(268432880)
- }
那麼它更快嗎? 總之,我們會發出熱情的“是”。
我們注意到一些人在 mandelbrot 集的幫助下進行基準測試,因此我們決定使用我們不久前創建的一個庫,該庫在 PHP 中繪製了一些不同類型的分形。 我們所做的只是生成三個分形並跟踪使用 microtome() 函數所花費的時間。 我們在下面顯示了 PHP 7.4.8 的結果。
- Burningship – 84.20269203186
- 曼德爾布洛特 – 21.552599906921
- 三角 - 32.685042858124
當我們在 PHP8 上運行相同的代碼時,它的速度要快得多。 數字會說明一切。 .
- Burningship – 15.272277116776
- 曼德勒布洛特 -3.7528541088104
- 三角 -4.4957919120789
這種巨大的速度提升非常有趣。 我們在這裡使用的代碼創建了巨大的分形,但我們記得在創建代碼時,我們大部分時間都花在等待分形的生成上。
這個添加對我們來說很有趣,因為我們在某些情況下(除了生成分形)將 PHP 推向了它的邊界。 我們可以注意到這對 PHP 的未來有很大的好處,並且允許在常規網站語言之外的情況下選擇該語言。
我們還沒有查看 WordPress 或 Drupal 等應用程序的速度增量,但從我們所讀到的內容來看,這些應用程序肯定沒有什麼不同。 將來,我們將在這些平台上運行基準測試,以確定 JIT 在那裡標記出什麼樣的差異。
聯合類型 ( RFC )
自 PHP7 以來,規定什麼樣的返回值和參數類型已經成為可能。 如果您傳遞的參數類型與預期類型不同,這將允許 PHP 拋出錯誤。 確保函數在 PHP 等鬆散類型語言中接收和生成完美類型的值至關重要。

在 PHP8 中,現在可以為參數和返回值規定各種類型,由管道字符分隔。
我們在下面展示了一個能夠接受浮點值或整數值的函數。
function addNumbers(int|float $number1, int|float $number2) : int|float { return $number1 + $number2; }
以前,需要在沒有任何類型提示的情況下生成與此相同的函數,因為 PHP 可以在傳遞的參數不正確的情況下靜默顯示類型。 這意味著如果我們將參數類型設置為整數,PHP 會將任何浮點值顯示為整數。 如果您不進行單元測試,它肯定會導致一些棘手的錯誤。
我們只是像其他任何人一樣調用它來使用上述功能
echo addNumbers(1, 1); // prints 2 echo addNumbers(1.1, 1.1); // prints 2.2
如果我們嘗試將字符串傳遞給相同的函數:
echo addNumbers('one', 'two');
我們將收到一個 PHP 致命錯誤,提示我們需要將浮點數或“int”傳遞給函數。
您不能將 void 類型用作聯合類型,因為它規定該函數將不返回任何內容。 簡單來說,你不能驚呼一個函數將返回一個 void 或一個整數。 你會收到一個 PHP 致命錯誤。
雖然我們可以看到一個正常的變化是這個特性被使用了一點,因為它之前只能在註釋中規定各種類型的值,這導致文檔塊註釋變得比代碼更詳細。
Nullsafe 運算符 (RFC)
除了 null 合併運算符之外,還可以直接從方法中檢測 null 返回值。 如果您不知道,null 合併運算符可以讓您獲取一個值,並且您不必測試該值是否存在,除了在第一個值為 null 的情況下返回不同的值。
因此,我們可以這樣做以從“$_GET superglobal”中獲取一個值。 如果該值不存在,則為“0”。
1. $page = $_GET['page'] ?? 0;
2. 回顯 $page;
null 安全運算符的工作方式是相同的,但允許您創建一個方便的快捷方式並在嘗試使用該值之前從某種方式測試 null 返回。
我們注意到這最終將在 Drupal 中有用,我們傾向於編寫大量檢查代碼以確保“方法的返回”或“對象屬性”在使用它們之前包含它們。 這是強制性的,因為 Drupal 中對象的上下文狀態是因為它們包含的內容。 這種變化肯定會簡化一些檢查。
命名參數 ( RFC )
命名參數允許您規定不同的參數順序和調用函數。 採用以下具有兩個參數的普通函數。 它將數組填充到給定的長度。
function fillArray(array $arrayToFill, int $number) : array { for ($i = 0 $i < $number; ++$i) { $arrayToFill[$i] =1; } return $arrayToFill; }
我們可以在它們定義的排列中傳遞參數,可以以正常方式調用此技術。
$newArray = fillArray([], 2);
從 PHP8 開始,您現在可以在將參數傳遞給函數時為其命名。 它還允許您以我們想要的任何順序發送參數。
$newArray = fillArray(number: 2, arrayToFill: []);
一個普通的例子,但它也可以使代碼更具可讀性。
這種技術適用於 PHP 中的所有函數,而不僅僅是用戶定義的函數。 在 PHP 語言中,數組和字符串函數的參數順序不同。 所以,這是一個非常受歡迎的補充。
屬性 V2 ( RFC1 RFC2 RFC3 )
屬性提供了一種將元數據與 PHP 類、函數、類屬性、函數參數和常量相連接的機制。 它們不能通過代碼直接訪問,您需要藉助 PHP 內置的反射類將它們拉出來。
ReflectionClass 類從 PHP5 開始就在 PHP 中; 但是,getAttribute() 方法是 PHP8 的新方法。 此方法返回一系列 ReflectionAttribute 對象,這些對象將包含有關屬性的信息。
這個添加面臨一些變化(我們可以從上面的多個 RFC 中註意到)。 如果您實例化該類,則可以使用 ReflectionClass 並打印出包含在類級別上的屬性信息。 PHP8 中有很多屬性,因此我們建議通讀 RFC 並熟悉它們的實際含義以及如何將它們集成到代碼中。
匹配表達式 ( RFC )
在 PHP 8 中,我們可以將新的匹配表達式與簡寫 switch 語句進行比較。
它看起來有點像一個函數聲明,它將根據傳入的值返回一個值。
當您希望在不包含多個if
語句的情況下檢查相同表達式的條件時,PHP 中的 switch 語句非常棒。
在這裡,我們對相同的表達式進行基本的if-else比較。
<?php if ($i == 'apple') { echo 'i is apple'; } elseif ($i == 'cake') { echo 'i is cake'; } else { echo 'i is pizza'; }
這就是我們前面示例的等效switch
語句的樣子
<?php switch ($i) { case 'apple': echo 'i is apple'; break; case 'cake': echo 'i is cake'; break; default: echo 'i is pizza'; }
資源“類”
資源“類”出現在 PHP 8 的主要更改列表中,並作為給定資源類型的不可實例化替代品。 請參閱下文以了解可用的替代品包括:
- CurlHandle — curl_init() 現在返回 CurlHandle,關聯一個 curl 資源。
- Socket / AddressInfo — 由套接字擴展提供; socket_*() 函數的數量返回一個 Socket,而 socket_address_info_lookup() 函數返回一個 AddressInfo 實例。
- GdImage — 它代表一個 GD 資源,由眾多 imagecreatefrom*() 函數恢復。
此外,重要的是要注意資源不會被 curl_close() 等函數破壞。 相反,您必須 unset() 實例以取消引用它,因為它現在是一個類實例。
您可以在函數和方法中將類指定為類型提示。
早些時候,我們必須返回無類型的值或保留資源參數並通過註釋記錄它們,但現在,您可以擁有顯式類型,這不僅使您的代碼更具可讀性,而且也更加類型安全。
這裡有什麼取捨?
如果您想使用 unset() 代替之前用於銷毀資源的函數來銷毀資源,您現在需要更新您的代碼。 這通常可以通過搜索和替換來完成。
反射 API 更改
PHP 8 中的另一個小但重要的變化與反射 API 有關。 在使用屬性系統時,您可以方便地通過反射 API 在任何反射類上檢索這些屬性。
隨著混合和聯合類型的添加,ReflectionParameter 技術 getClass()、isCallable() 和 isArray() 現在已棄用。
之所以如此,是因為使用 getType() 會好得多,並且您可以獲得特定參數滿足的類型的完整列表。
PHP 8 語法改進
正如我們所注意到的,JIT 佔據了頭條。 PHP 8 語法改進為 PHP 開發人員提供了巨大的生活質量優勢。
無論是消除提升的構造函數參數中的通用樣板,還是改進的異常和錯誤處理,開發人員都會感到興奮。
- “混合”偽類型
- 聯合類型
- 類構造函數屬性提升
- 從表達式中拋出異常
- 屬性
- ::類無處不在
- 僅按類型捕獲
- 匹配表達式
為了維護本博客的目的,我們將重點放在聯合類型、屬性和匹配表達式上。
聯合類型
聯合類型表明一個值是兩個或多個指定排序之一。 它是通過在每個允許的類型之間放置一個垂直條來完成的。 對於一些深入研究 PHP 註釋以返回值或指定參數的開發人員來說,您很可能已經在這樣做了。
對於接受很多不同類型或返回很多不同類型的人來說,聯合類型會帶來很多複雜性和理解困難。
儘管如此,這在某些情況下還是非常有用的。 例如,如果您可以接受實現新 Stringable 接口的對象(“string|Stringable”)或字符串,或者如果您可以接受實現 Traversable 接口的對象(“array|Traversable”)或數組.
匹配表達式
匹配表達式切斷了與確定在給定 switch case 內中斷是否是故意相關的猜測。 它還簡化了基於匹配分配值的正常模式。
使用時,我們傳遞給 match() 的值將直接與左側的表達式進行比較。 無論是表達式還是值,傳遞給 match() 的值都應該匹配它才能被選擇。
匹配時,估計右邊的表達式,並返回它的返回值,而表達式只能是 Lambda 或 callables 函數,不允許多行閉包。
屬性
PHP 8 也集成了語言級別的屬性。 雖然屬性通過 docblock 註釋存在超過 15 年,但將它們內置到語言中可以提供更好的性能,當然,更強大的功能和更高的一致性。 我們可能會看到屬性在工具和快速應用程序的開發中得到高度使用。
結論
當然,PHP8 有一些變化。 儘管如此,看起來大部分被棄用的代碼都是針對我們最近沒有看到使用的舊功能。
我們非常感興趣地註意到 JIT 引擎將對我們使用的代碼庫以及更廣泛的 PHP 社區產生什麼影響——我們強烈建議您掃描您的代碼庫以查找與 PHP8 的不兼容性並運行單元測試以確保您的 PHP在對升級說“是”之前,應用程序可以完美運行。