PHP 8.4有何新特性和改進?

what-is-new-in-php-8-4-1024x512-1

空氣中瀰漫著秋冬的味道,所以是時候推出 PHP 的新版本了,PHP 是一種伺服器端指令碼語言,為我們最喜歡的內容管理系統 WordPress 提供動力。在 11 月 21 日釋出 8.4 GA 版本之前,PHP 開發人員釋出了許多新程式碼庫的早期版本,包括 8 月份功能凍結後的一些候選釋出版本。

除了新功能、改進和棄用之外,我們預計 2024 年的 PHP 釋出週期也會有所調整,所有當前支援版本的安全釋出結束時間將同步到年底,而不是其 GA 生日。

更重要的是,支援時間延長了一年,這意味著 PHP 8.4 可以安全地使用到 2028 年(其中兩年是安全和錯誤修復,兩年只是安全修復)。

雖然您可以花更多時間使用 PHP 8.4,但您可能現在就想了解該版本的新功能。

PHP 8.4新功能和改進

與 8.4 中的一些新增功能相比,去年釋出的 PHP 8.3 中的新功能顯得有些低調:

  1. 屬性鉤子
  2. 非對稱可見性
  3. 不帶括號的鏈式呼叫 new
  4. 查詢陣列項的新函式
  5. HTML5 解析
  6. 多位元組修剪函式

屬性鉤子

屬性鉤子為處理PHP 物件導向程式設計(OOP)中的 “getters ”和 “setters ”帶來了全新的方法,使您可以簡化類檔案的結構。

下面的簡單類中包含了$size$flavor 屬性,作為屬性鉤子可以替代的示例。它們具有私有可見性,以保護它們不被結果物件外部直接訪問。這就是為什麼公共 getter 和 setter 方法會調解對屬性的訪問:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class Coffee
{
private string $size;
private string $flavor;
public function __construct(string $size, string $flavor) {
$this->size = $size;
$this->flavor = $flavor;
}
// "Setting" coffee size and flavor
public function setSize(string $size): void {
$this->size = $size;
}
public function setFlavor(string $flavor): void {
$this->flavor = $flavor;
}
// "Getting" coffee size and flavor
public function getSize(): string {
return $this->size;
}
public function getFlavor(): string {
return $this->flavor;
}
} // End of class
// Make some coffee
$coffee = new Coffee('Small', 'Pumpkin Spice');
print $coffee->getSize() . ' ' . $coffee->getFlavor(); // Prints "Small Pumpkin Spice"
// Change order
$coffee->setSize('Grande');
$coffee->setFlavor('Mocha');
print $coffee->getSize() . ' ' . $coffee->getFlavor(); // Prints "Grande Mocha"
class Coffee { private string $size; private string $flavor; public function __construct(string $size, string $flavor) { $this->size = $size; $this->flavor = $flavor; } // "Setting" coffee size and flavor public function setSize(string $size): void { $this->size = $size; } public function setFlavor(string $flavor): void { $this->flavor = $flavor; } // "Getting" coffee size and flavor public function getSize(): string { return $this->size; } public function getFlavor(): string { return $this->flavor; } } // End of class // Make some coffee $coffee = new Coffee('Small', 'Pumpkin Spice'); print $coffee->getSize() . ' ' . $coffee->getFlavor(); // Prints "Small Pumpkin Spice" // Change order $coffee->setSize('Grande'); $coffee->setFlavor('Mocha'); print $coffee->getSize() . ' ' . $coffee->getFlavor(); // Prints "Grande Mocha"
class Coffee
{
private string $size;
private string $flavor;
public function __construct(string $size, string $flavor) {
$this->size   = $size;
$this->flavor = $flavor;
}
// "Setting" coffee size and flavor
public function setSize(string $size): void {
$this->size = $size;
}
public function setFlavor(string $flavor): void {
$this->flavor = $flavor;
}
// "Getting" coffee size and flavor
public function getSize(): string {
return $this->size;
}
public function getFlavor(): string {
return $this->flavor;
}
} // End of class
// Make some coffee
$coffee = new Coffee('Small', 'Pumpkin Spice');
print $coffee->getSize() . ' ' . $coffee->getFlavor(); // Prints "Small Pumpkin Spice"
// Change order
$coffee->setSize('Grande');
$coffee->setFlavor('Mocha');
print $coffee->getSize() . ' ' . $coffee->getFlavor(); // Prints "Grande Mocha"

或者,也許你的類有許多屬性,與其編寫許多 getter 和 setter 方法,不如使用 PHP 的 _get_set 神奇方法。你甚至可以像下面這段摘錄一樣,用一個有點亂的 switch 語句來處理事情。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// __set magic method example
public function __set(string $key, $value): void
switch ($key) {
case 'size':
$this->size = $value;
break;
case 'flavor':
$this->flavor = $value;
break;
default:
throw new InvalidArgumentException('Invalid input');
}
}
// Later, we can change the coffee order like this:
$coffee->size = 'Grande';
$coffee->flavor = 'Mocha';
// __set magic method example public function __set(string $key, $value): void switch ($key) { case 'size': $this->size = $value; break; case 'flavor': $this->flavor = $value; break; default: throw new InvalidArgumentException('Invalid input'); } } // Later, we can change the coffee order like this: $coffee->size = 'Grande'; $coffee->flavor = 'Mocha';
// __set magic method example
public function __set(string $key, $value): void 
switch ($key) {
case 'size':
$this->size = $value;
break;
case 'flavor':
$this->flavor = $value;
break;
default:
throw new InvalidArgumentException('Invalid input');
}
}
// Later, we can change the coffee order like this:
$coffee->size = 'Grande';
$coffee->flavor = 'Mocha';

無論您選擇哪種方法,類中的屬性越多,用於操作這些屬性的程式碼就離類檔案頂部的定義越遠。此外,某些 _get_set 魔法方法的實現可能會意外地提供對物件中的私有或受保護屬性的訪問許可權,而這些屬性本不是你打算公開的。

新的屬性鉤子功能將獲取器和設定器功能與屬性本身捆綁在一起。在下面的屬性鉤子示例中,你會注意到 Coffee 類的 $size$flavor 屬性現在是公開的。不過,我們也為 set 鉤子新增了一些基本驗證,使其有別於直接賦值。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Property definitions at the top of our Coffee class
class Coffee
{
public string $flavor {
set(string $value) {
if (strlen($value) > 16) throw new InvalidArgumentException('Input is too long');
$this->flavor = $value;
}
}
public string $size {
set(string $value) {
if (! in_array($value, array(‘Small’, ‘Grande’))) throw new InvalidArgumentException('Not a valid size');
$this->size = $value;
}
}
// Rest of the Coffee class
}
// Define a coffee
$coffee = new Coffee();
$coffee->size = 'Grande';
$coffee->flavor = 'Pumpkin spice';
// Property definitions at the top of our Coffee class class Coffee { public string $flavor { set(string $value) { if (strlen($value) > 16) throw new InvalidArgumentException('Input is too long'); $this->flavor = $value; } } public string $size { set(string $value) { if (! in_array($value, array(‘Small’, ‘Grande’))) throw new InvalidArgumentException('Not a valid size'); $this->size = $value; } } // Rest of the Coffee class } // Define a coffee $coffee = new Coffee(); $coffee->size = 'Grande'; $coffee->flavor = 'Pumpkin spice';
// Property definitions at the top of our Coffee class
class Coffee
{
public string $flavor {
set(string $value) {
if (strlen($value) > 16) throw new InvalidArgumentException('Input is too long');
$this->flavor = $value;
}
}
public string $size {
set(string $value) {
if (! in_array($value, array(‘Small’, ‘Grande’))) throw new InvalidArgumentException('Not a valid size');
$this->size = $value;
}
}
// Rest of the Coffee class
}
// Define a coffee
$coffee = new Coffee();
$coffee->size = 'Grande';
$coffee->flavor = 'Pumpkin spice';

同樣,如下所示,get 鉤子可以將功能打包到看似普通的物件屬性引用中。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Simplified Coffee class
class Coffee
{
public string $flavor {
get {
return $this->flavor . ' Spice';
}
}
}
// Create a flavor
$coffee = new Coffee();
$coffee->flavor = 'Pumpkin'; // Stores the value "Pumpkin"
print $coffee->flavor; // Prints "Pumpkin Spice"
// Simplified Coffee class class Coffee { public string $flavor { get { return $this->flavor . ' Spice'; } } } // Create a flavor $coffee = new Coffee(); $coffee->flavor = 'Pumpkin'; // Stores the value "Pumpkin" print $coffee->flavor; // Prints "Pumpkin Spice"
// Simplified Coffee class
class Coffee
{
public string $flavor {
get { 
return $this->flavor . ' Spice';
}
}
}
// Create a flavor 
$coffee = new Coffee();
$coffee->flavor = 'Pumpkin'; // Stores the value "Pumpkin"
print $coffee->flavor;       // Prints "Pumpkin Spice"

與 PHP 魔術方法不同,屬性鉤子可以在介面和抽象類中使用。介面示例:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
interface Coffee
{
public string $size { get; set; }
public string $flavor { get; set; }
}
interface Coffee { public string $size { get; set; } public string $flavor { get; set; } }
interface Coffee
{
public string $size { get; set; }
public string $flavor { get; set; }
}

不對稱可見性

我們前面提到的公開可見的 getter 和 setter 方法是訪問類中私有和受保護屬性的傳統方法。

PHP 8.4 的一個有趣特性是,根據訪問的上下文,屬性可以有不同級別的可見性。因此,一個屬性在被讀取時可能是公開的,但在被設定時可能是私有或受保護的。

看看這個:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class Coffee
{
public private(set) string $flavor = 'Pumpkin Spice';
}
$coffee = new Coffee();
print $coffee->flavor; // Prints "Pumpkin Spice"
$coffee->flavor = 'Mocha'; // Error (visibility)
class Coffee { public private(set) string $flavor = 'Pumpkin Spice'; } $coffee = new Coffee(); print $coffee->flavor; // Prints "Pumpkin Spice" $coffee->flavor = 'Mocha'; // Error (visibility)
class Coffee
{
public private(set) string $flavor = 'Pumpkin Spice';
}
$coffee = new Coffee();
print $coffee->flavor;     // Prints "Pumpkin Spice"
$coffee->flavor = 'Mocha';  // Error (visibility)

在上文中,該類的 $flavor 屬性是公開的,但在設定上下文中除外。這已經很簡單了,但非對稱可見性甚至還有一點捷徑:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class Coffee
{
// public is assumed when the context is not setting
private(set) string $flavor = 'Pumpkin Spice';
}
class Coffee { // public is assumed when the context is not setting private(set) string $flavor = 'Pumpkin Spice'; }
class Coffee
{
// public is assumed when the context is not setting
private(set) string $flavor = 'Pumpkin Spice';
}

您可以結合使用屬性鉤子和非對稱可見性,從而在處理各種可見性的物件屬性時獲得極大的靈活性。

不帶括號的鏈式呼叫 new

說到速記,以前呼叫 new 和鏈式方法時需要將其呼叫放在括號中,就像下面這樣:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$coffee = (new Coffee())->getFlavor()->getSize();
$coffee = (new Coffee())->getFlavor()->getSize();
$coffee = (new Coffee())->getFlavor()->getSize();

PHP 8.4 允許這樣做:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$coffee = new Coffee()->getFlavor()->getSize();
$coffee = new Coffee()->getFlavor()->getSize();
$coffee = new Coffee()->getFlavor()->getSize();

這看似是一個微小的改動,但去掉兩個括號後,閱讀和除錯都變得更加容易。

查詢陣列項的新函式

PHP 8.4 引入了函式 array_find(),它可以搜尋陣列元素中與回撥函式中表達的條件相匹配的成員。該函式返回符合回撥測試的第一個元素的值。

新版本還包括其他三個相關函式:

  • array_find_key() :與 array_find() 類似,但返回值是匹配元素的 key,而不是元素本身的值。
  • array_all():如果被測試陣列中的每個元素符合回撥測試,則返回 true
  • array_any():如果陣列中至少有一個元素符合回撥測試,則返回true

請注意,後兩個函式返回的是布林指標,而不是陣列鍵或內容。

下面是一些快速示例:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$array = [
'a' => 'Mocha',
'b' => 'Caramel',
'c' => 'Maple',
'd' => 'Pumpkin'
];
// Find the first flavor name that is 5 characters long
var_dump(array_find($array, function (string $value) {
return strlen($value) == 5;
})); // Returns “Mocha,” even though “Maple” is the same length
// Find the array key for the first flavor with a name longer than 5 characters.
var_dump(array_find_key($array, function (string $value) {
return strlen($value) > 5;
})); // Returns “b”
// Check to see if any flavor name is less than 5 characters long
var_dump(array_any($array, function (string $value) {
return strlen($value) < 5;
})); // Returns false
// Check to see if all flavor names are shorter than 8 characters
var_dump(array_all($array, function (string $value) {
return strlen($value) < 8;
})); // Returns true
$array = [ 'a' => 'Mocha', 'b' => 'Caramel', 'c' => 'Maple', 'd' => 'Pumpkin' ]; // Find the first flavor name that is 5 characters long var_dump(array_find($array, function (string $value) { return strlen($value) == 5; })); // Returns “Mocha,” even though “Maple” is the same length // Find the array key for the first flavor with a name longer than 5 characters. var_dump(array_find_key($array, function (string $value) { return strlen($value) > 5; })); // Returns “b” // Check to see if any flavor name is less than 5 characters long var_dump(array_any($array, function (string $value) { return strlen($value) < 5; })); // Returns false // Check to see if all flavor names are shorter than 8 characters var_dump(array_all($array, function (string $value) { return strlen($value) < 8; })); // Returns true
$array = [
'a' => 'Mocha',
'b' => 'Caramel',
'c' => 'Maple',
'd' => 'Pumpkin'
];
// Find the first flavor name that is 5 characters long
var_dump(array_find($array, function (string $value) {
return strlen($value) == 5;
})); // Returns “Mocha,” even though “Maple” is the same length 
// Find the array key for the first flavor with a name longer than 5 characters.
var_dump(array_find_key($array, function (string $value) {
return strlen($value) > 5;
})); // Returns “b”
// Check to see if any flavor name is less than 5 characters long
var_dump(array_any($array, function (string $value) {
return strlen($value) < 5;
})); // Returns false
// Check to see if all flavor names are shorter than 8 characters
var_dump(array_all($array, function (string $value) {
return strlen($value) < 8;
})); // Returns true

HTML5解析

HTM5 是現代網頁結構的事實標準,但 PHP 的文件物件模型 (DOM) 解析技術卻在 HTML 4.01 時停滯不前。

PHP 8.4 並沒有升級現有的 DOMDocument 類(該類適用於較舊的 HTML 標準),而是提供了一個新的 DomHTMLDocument 類,該類已為 HTM5 做好準備。

您可以像這樣匯入 HTML5 頁面的內容:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$document = DomHTMLDocument::createFromString($html)
$document = DomHTMLDocument::createFromString($html)
$document = DomHTMLDocument::createFromString($html)

除了上述 createFromString($html) 建構函式外,該類還支援 createFromFile($path)createEmpty() 函式。

新的解析器能識別諸如 mainarticlesection 等語義 HTML5 標記,這些標記現在已為大多數人所熟悉。

多位元組修剪函式

PHP 8.4 中的另一個新增功能似乎是姍姍來遲,那就是在修剪函式中支援多位元組:

  • mb_trim()
  • mb_ltrim()
  • mb_rtrim()

與使用已久的 PHP trim() 函式一樣,mb_trim 會從可能包含多位元組字元的字串兩端刪除空白和一些特殊字元(如換行符)。其他函式要麼修剪字串的左端,要麼修剪字串的右端。

PHP 8.4中的棄用

PHP 的每個版本都會帶來一份功能和函式清單(有些非常晦澀難懂),這些功能和函式會被標記為最終將從平臺中刪除。PHP 8.4 中一個比較引人注目的棄用是非 Cookie 會話跟蹤。

GET/POST會話的棄用

雖然 Cookie 通常是跟蹤使用者會話的首選方法,但 PHP 支援在 GET/POST 引數中固定會話 ID 資料。要通過 URL 中的引數啟用會話跟蹤,需要禁用 PHP 設定 session.use_only_cookies,並啟用設定 session.use_trans_sid

在 PHP 8.4 中,這些設定的任何一種狀態都會觸發網站日誌中可能出現的棄用警告。PHP 9 釋出後,這些設定將不再可用。

PHP 8.4中的其他棄用(和移除)

下面列出了 PHP 8.4 開發團隊計劃棄用的功能。(其中一些功能包含指向更多資訊的連結)

  • 正式廢棄的 DOMDocumentDOMEntity 屬性。
  • 移除了 DOMImplementation::getFeature($feature,$version)
  • 棄用 DOM_PHP_ERR 常量。
  • 棄用 unserialize 中的“S”標記。
  • 棄用 session.sid_lengthsession.sid_bits_per_character
  • 棄用 SplFixedArray::__wakeup()
  • 廢止使用字串方法名的 xml_set_object()xml_set_*_handler()
  • 禁止向 dba_key_split() 傳遞 null 和 false。
  • 禁止為 ext/hash 函式的選項傳遞錯誤的資料型別。
  • 停用常量 SUNFUNCS_RET_STRINGSUNFUNCS_RET_DOUBLESUNFUNCS_RET_TIMESTAMP
  • 廢止專有的 CSV 轉義機制。
  • 棄用 E_STRICT 常量。
  • 棄用 strtok()
  • 停用從使用者輸出處理程式返回非字串值的功能。
  • 取消在使用者輸出處理程式中產生輸出。
  • 取消使用陣列 $datafile_put_contents()
  • 停用 mysqli_ping()mysqli::ping()
  • 棄用 mysqli_refresh()
  • 停用 mysqli_kill()
  • 停用 mysqli_store_result() 的第二個引數。
  • 停用 lcg_value()函式
  • 停用 uniqid()
  • 停用 md5(), sha1(), md5_file()sha1 _ file()
  • 不再向 trigger_error() 傳遞 E_USER_ERROR 訊息。
  • 不再使用單個下劃線 (“_”) 作為類名。
  • 廢棄將 SOAP_FUNCTIONS_ALL 常量傳遞給 SoapServer::addFunction() 的做法。

PHP 的每一個版本都會帶來一份功能和函式(有些非常晦澀難懂)的廢棄列表,這些功能和函式將被標記為最終從平臺中刪除。

小結

PHP 8.4 帶來了一些有趣的變化。相信一些評測機構或者開發人員將很快測試該版本,比如 PHP 基準測試-對各種基於 PHP 的內容管理系統進行的測試。

我們也很想知道開發人員何時開始將 PHP 8.4 的一些新特性(尤其是屬性鉤子)應用到他們的專案中。

您最喜歡 PHP 8.4 的哪些功能?請在評論中分享您的想法!

評論留言