PHP 8.3:最新版本中的新功能和變化

PHP 8.3:最新版本中的新功能和變化

PHP 8.3 於 11 月 23 日如期釋出,它包含了自 PHP 8.2 釋出以來的許多新功能和改進。儘管官方認為這只是一個次要版本,但 8.3 中的一些變化可能會直接影響到你的 PHP 工作-也許能幫助你更快地編寫程式碼,減少 bug。

讓我們深入瞭解一下這個最新版本所帶來的重大變化(有時並不那麼重大)。

PHP 8.3 的新功能和改進

讓我們先來看看 PHP 8.3 中最引人注目的功能。

  1. 型別類常量
  2. 新的 json_validate() 函式
  3. 深度克隆 readonly 屬性
  4. 新的 #[\Override] 屬性
  5. 動態獲取類常量和列舉成員
  6. 新的 getBytesFromString() 方法
  7. 新的 getFloat() 和 nextFloat() 方法

型別類常量

從 PHP 7.4 開始,我們就可以為類屬性宣告型別了。然而,儘管多年來對 PHP 型別進行了多次調整,但直到現在才擴充套件到常量。

在 PHP 8.3 中,類常量(也包括介面、特質和列舉常量)可以進行型別化,這樣開發人員就不太可能偏離常量最初宣告的意圖了。

下面是一個使用介面的基本示例:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Legal:
interface ConstTest {
// Declared type and value are both strings
const string VERSION = "PHP 8.3";
}
// Illegal:
interface ConstTest {
// Type and value mismatch in this initial declaration
const float VERSION = "PHP 8.3";
}
// Legal: interface ConstTest { // Declared type and value are both strings const string VERSION = "PHP 8.3"; } // Illegal: interface ConstTest { // Type and value mismatch in this initial declaration const float VERSION = "PHP 8.3"; }
// Legal:
interface ConstTest {
// Declared type and value are both strings
const string VERSION = "PHP 8.3";
}
// Illegal:
interface ConstTest {
// Type and value mismatch in this initial declaration
const float VERSION = "PHP 8.3";
}

這些型別化類常量的真正價值是在處理從基本宣告派生出來的類時顯現出來的。雖然子類可以經常為常量賦新值,但 PHP 8.3 可以幫助防止意外改變常量的型別,使其與初始宣告不相容:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class ConstTest {
const string VERSION = "PHP 8.2";
}
class MyConstTest extends ConstTest {
// Legal:
// It's OK to change the value of VERSION here
const string VERSION = "PHP 8.3";
// Illegal:
// Type must be declared if it was specified in the base class
const VERSION = "PHP 8.3";
// Illegal:
// In this case, we can't change the type declared in the
// base class, even if the new type and its value are compatible.
const float VERSION = 8.3;
}
class ConstTest { const string VERSION = "PHP 8.2"; } class MyConstTest extends ConstTest { // Legal: // It's OK to change the value of VERSION here const string VERSION = "PHP 8.3"; // Illegal: // Type must be declared if it was specified in the base class const VERSION = "PHP 8.3"; // Illegal: // In this case, we can't change the type declared in the // base class, even if the new type and its value are compatible. const float VERSION = 8.3; }
class ConstTest {
const string VERSION = "PHP 8.2";
}
class MyConstTest extends ConstTest {
// Legal:
// It's OK to change the value of VERSION here
const string VERSION = "PHP 8.3";
// Illegal:
// Type must be declared if it was specified in the base class
const VERSION = "PHP 8.3";
// Illegal:
// In this case, we can't change the type declared in the 
// base class, even if the new type and its value are compatible.
const float VERSION = 8.3;
}

請記住,在 “narrowing” 多個型別或使用其他相容型別時,分配給類常量的型別可能會不同:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class ConstTest {
const string|float VERSION = "PHP 8.2";
}
class MyConstTest extends ConstTest {
// Legal:
// Here, it's OK to narrow the type declaration to string or float
const string VERSION = "PHP 8.3";
const float VERSION = 8.3;
// Legal:
// Value could be an int, but it's compatible with float
const float VERSION = 8;
// Illegal:
// We can't widen the type options here to include int
const string|float|int VERSION = 8;
}
class ConstTest { const string|float VERSION = "PHP 8.2"; } class MyConstTest extends ConstTest { // Legal: // Here, it's OK to narrow the type declaration to string or float const string VERSION = "PHP 8.3"; const float VERSION = 8.3; // Legal: // Value could be an int, but it's compatible with float const float VERSION = 8; // Illegal: // We can't widen the type options here to include int const string|float|int VERSION = 8; }
class ConstTest {
const string|float VERSION = "PHP 8.2";
}
class MyConstTest extends ConstTest {
// Legal:
// Here, it's OK to narrow the type declaration to string or float
const string VERSION = "PHP 8.3";
const float VERSION = 8.3;
// Legal:
// Value could be an int, but it's compatible with float 
const float VERSION = 8;
// Illegal:
// We can't widen the type options here to include int
const string|float|int VERSION = 8;
}

在驗證返回值時,其他屬性支援的兩種型別  – voidnever – 不支援作為類常量型別。

新的 json_validate() 函式

在處理 JSON 編碼資料時,很高興能在嘗試對其進行處理之前知道有效載荷在語法上是否有效。

在以前的 PHP 版本中,開發人員使用 json_decode() 函式,並在該函式嘗試將 JSON 資料轉換為關聯陣列或物件時檢查錯誤。PHP 8.3 的新 json_validate() 函式可以在不使用構建陣列或物件結構所需的全部記憶體的情況下進行錯誤檢查。

因此,在過去,您可能會像這樣驗證 JSON 有效負載:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$obj = json_decode($maybeJSON);
if (json_last_error() === JSON_ERROR_NONE) {
// Probably do something with $obj
}
$obj = json_decode($maybeJSON); if (json_last_error() === JSON_ERROR_NONE) { // Probably do something with $obj }
$obj = json_decode($maybeJSON);
if (json_last_error() === JSON_ERROR_NONE) {
// Probably do something with $obj   
}

如果您不打算立即對上例中的 $obj 執行操作,那麼僅僅確認原始 JSON 有效負載的有效性就會佔用大量資源。在 PHP 8.3 中,您可以這樣做來節省一些記憶體:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
if (json_validate($maybeJSON) {
// Do something with $maybeJSON
}
if (json_validate($maybeJSON) { // Do something with $maybeJSON }
if (json_validate($maybeJSON) {
// Do something with $maybeJSON   
}

注:使用 json_validate() ,然後立即通過 json_decode() 執行資料,無論如何都會佔用 decode 的記憶體資源,這樣做意義不大。您更有可能使用新函式來驗證 JSON,然後再將其儲存到某個地方或作為請求響應交付。

深度克隆 readonly 屬性

將單個類屬性宣告為 readonly 的功能出現在 PHP 8.1 中。PHP 8.2 引入了將該屬性分配給整個類的功能。然而,許多開發人員認為,在使用包含此類屬性的類時受到的限制妨礙了有用的程式設計。

This code example from the RFC shows how it works:

一份關於修改 readonly 行為的 RFC 提出了兩項建議:

  1. 允許非只讀類擴充套件 readonly
  2. 允許在克隆時重新初始化 readonly 屬性

第二項建議已被引入 PHP 8.3。新方法允許在 __clone 魔術方法中(包括通過在 __clone 中呼叫的函式)重新初始化具有 readonly 屬性的類例項。

RFC 中的程式碼示例展示了它是如何工作的:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class Foo {
public function __construct(
public readonly DateTime $bar,
public readonly DateTime $baz
) {}
public function __clone() {
// $bar will get a new DateTime when clone is invoked
$this->bar = clone $this->bar;
// And this function will be called
$this->cloneBaz();
}
private function cloneBaz() {
// This is legal when called from within __clone
unset($this->baz);
}
}
$foo = new Foo(new DateTime(), new DateTime());
$foo2 = clone $foo;
class Foo { public function __construct( public readonly DateTime $bar, public readonly DateTime $baz ) {} public function __clone() { // $bar will get a new DateTime when clone is invoked $this->bar = clone $this->bar; // And this function will be called $this->cloneBaz(); } private function cloneBaz() { // This is legal when called from within __clone unset($this->baz); } } $foo = new Foo(new DateTime(), new DateTime()); $foo2 = clone $foo;
class Foo {
public function __construct(
public readonly DateTime $bar,
public readonly DateTime $baz
) {}
public function __clone() {
// $bar will get a new DateTime when clone is invoked
$this->bar = clone $this->bar; 
// And this function will be called
$this->cloneBaz();
}
private function cloneBaz() {
// This is legal when called from within __clone
unset($this->baz); 
}
}
$foo = new Foo(new DateTime(), new DateTime());
$foo2 = clone $foo;

新的 #[\Override] 屬性

在 PHP 中實現介面時,程式設計師會為這些介面中命名的方法提供詳細的功能。在建立類的例項時,程式設計師可以通過在子類中建立具有相同名稱和相容簽名的替代版本來覆蓋父類方法。

一個問題是,程式設計師可能會認為他們是在實現介面方法或覆蓋父方法,其實不然。由於子類方法的名稱有錯別字,或者由於父類程式碼中的方法被刪除或重新命名,他們可能會建立一個完全獨立的怪獸。

PHP 8.3 引入了 #[\Override] 屬性,以幫助程式設計師明確方法必須在程式碼中具有一定的沿襲關係。

下面是一個基本例子:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class A {
protected function ovrTest(): void {}
}
// This will work because ovrTest()
// can be found in the parent class
class B extends A {
#[\Override]
public function ovrTest(): void {}
}
// This will fail because ovrBest()
// (probably a typo) is not in the parent
class C extends A {
#[\Override]
public function ovrBest(): void {}
}
class A { protected function ovrTest(): void {} } // This will work because ovrTest() // can be found in the parent class class B extends A { #[\Override] public function ovrTest(): void {} } // This will fail because ovrBest() // (probably a typo) is not in the parent class C extends A { #[\Override] public function ovrBest(): void {} }
class A {
protected function ovrTest(): void {}
}
// This will work because ovrTest() 
// can be found in the parent class
class B extends A {
#[\Override]
public function ovrTest(): void {}
}
// This will fail because ovrBest() 
// (probably a typo) is not in the parent
class C extends A {
#[\Override]
public function ovrBest(): void {}
}

動態獲取類常量和列舉成員

與 PHP 程式碼中的其他屬性不同,使用變數名獲取類常量和列舉成員有點複雜。在 PHP 8.3 之前,您可能會使用 constant() 函式來獲取常量,如下所示:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class MyClass {
public const THE_CONST = 9;
}
enum MyEnum int {
case FirstMember = 9;
case SecondMember = 9;
}
$constantName = 'THE_CONST';
$memberName = 'FirstMember';
echo constant('MyClass::' . $constantName);
echo constant('MyEnum::' . $memberName)->value;
class MyClass { public const THE_CONST = 9; } enum MyEnum int { case FirstMember = 9; case SecondMember = 9; } $constantName = 'THE_CONST'; $memberName = 'FirstMember'; echo constant('MyClass::' . $constantName); echo constant('MyEnum::' . $memberName)->value;
class MyClass {
public const THE_CONST = 9;
}
enum MyEnum int {
case FirstMember = 9;
case SecondMember = 9;
}
$constantName = 'THE_CONST';
$memberName = 'FirstMember';
echo constant('MyClass::' . $constantName);
echo constant('MyEnum::' . $memberName)->value;

現在,使用上面相同的類和列舉定義,就可以通過 PHP 8.3 的動態常量獲取功能實現相同的結果:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$constantName = 'THE_CONST';
$memberName = 'FirstMember';
echo MyClass::{$constantName};
echo MyEnum::{$memberName}->value;
$constantName = 'THE_CONST'; $memberName = 'FirstMember'; echo MyClass::{$constantName}; echo MyEnum::{$memberName}->value;
$constantName = 'THE_CONST';
$memberName = 'FirstMember';
echo MyClass::{$constantName};
echo MyEnum::{$memberName}->value;

新的 getBytesFromString() 方法

您是否曾想過使用預先批准的字符集生成隨機字串?現在有了 getBytesFromString() 方法,您就可以輕鬆實現這個願望了,該方法已新增到 PHP 8.3 的隨機擴充套件中。

這個新方法很簡單:將字串作為源材料傳給它,並指定要使用的字元個數。然後,該方法將從字串中隨機選擇位元組,直到達到指定長度為止。

下面是一個簡單的示例:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$rando = new Random\Randomizer();
$alpha = 'ABCDEFGHJKMNPQRSTVWXYZ';
$rando->getBytesFromString($alpha, 6); // "MBXGWL"
$rando->getBytesFromString($alpha, 6); // "LESPMG"
$rando->getBytesFromString($alpha, 6); // "NVHWXC"
$rando = new Random\Randomizer(); $alpha = 'ABCDEFGHJKMNPQRSTVWXYZ'; $rando->getBytesFromString($alpha, 6); // "MBXGWL" $rando->getBytesFromString($alpha, 6); // "LESPMG" $rando->getBytesFromString($alpha, 6); // "NVHWXC"
$rando = new Random\Randomizer();
$alpha = 'ABCDEFGHJKMNPQRSTVWXYZ';
$rando->getBytesFromString($alpha, 6); //  "MBXGWL"
$rando->getBytesFromString($alpha, 6); //  "LESPMG"
$rando->getBytesFromString($alpha, 6); //  "NVHWXC"

隨機輸出所要求的長度有可能比輸入字串的位元組數還多:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$rando = new Random\Randomizer();
$nums = '123456';
$rando->getBytesFromString($nums, 10); // "2526341615"
$rando = new Random\Randomizer(); $nums = '123456'; $rando->getBytesFromString($nums, 10); // "2526341615"
$rando = new Random\Randomizer();
$nums = '123456';
$rando->getBytesFromString($nums, 10); //  "2526341615"

輸入字串中的每個字元都是獨一無二的,每個字元被隨機結果選中的機率相等。不過,可以通過讓輸入字元比其他字元出現得更頻繁來對它們進行加權。例如

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$rando = new Random\Randomizer();
$weighted = 'AAAAA12345';
$rando->getBytesFromString($weighted, 5); // "1AA53"
$rando->getBytesFromString($weighted, 10); // "42A5A1AA3A"
$rando = new Random\Randomizer(); $weighted = 'AAAAA12345'; $rando->getBytesFromString($weighted, 5); // "1AA53" $rando->getBytesFromString($weighted, 10); // "42A5A1AA3A"
$rando = new Random\Randomizer();
$weighted = 'AAAAA12345';
$rando->getBytesFromString($weighted, 5); //  "1AA53"
$rando->getBytesFromString($weighted, 10); //  "42A5A1AA3A"

新的 getFloat()nextFloat() 方法

在擴充套件隨機擴充套件的同時,PHP 8.3 引入了兩個生成隨機浮點數值的新方法: getFloat()nextFloat()

下面是一個例子:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$rando = new Random\Randomizer();
// Generate a float value between a minimum
// value of 0 and a maximum value of 5
$rando->getFloat(0,5); // 2.3937446906217
$rando = new Random\Randomizer(); // Generate a float value between a minimum // value of 0 and a maximum value of 5 $rando->getFloat(0,5); // 2.3937446906217
$rando = new Random\Randomizer();
// Generate a float value between a minimum 
//  value of 0 and a maximum value of 5
$rando->getFloat(0,5); // 2.3937446906217

getFloat() 方法在最小值和最大值之後還接受第三個引數。使用 Random\IntervalBoundary 列舉可以確定函式是否可以返回最小值和最大值。

以下是規則:

  • IntervalBoundary::ClosedOpen: 可以返回最小值,但不能返回最大值
  • IntervalBoundary::ClosedClosed: 可以返回最小值和最大值
  • IntervalBoundary::OpenClosed: 不能返回最小值,可以返回最大值
  • IntervalBoundary::OpenOpen: 既不返回最小值,也不返回最大值

使用 getFloat() 而不指定 Enum 作為第三個引數時,預設值是 IntervalBoundary::ClosedOpen

新函式的文件中提供了一個有用的示例,可以隨機生成經緯度座標,其中緯度可以包括 -90 和 90,但經度不能包括 -180 和 180(因為它們是相同的):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$rando = new Random\Randomizer();
printf(
"Lat: %+.6f Long: %+.6f",
$rando->getFloat(-90, 90, \Random\IntervalBoundary::ClosedClosed),
// -180 will not be used
$rando->getFloat(-180, 180, \Random\IntervalBoundary::OpenClosed),
);
$rando = new Random\Randomizer(); printf( "Lat: %+.6f Long: %+.6f", $rando->getFloat(-90, 90, \Random\IntervalBoundary::ClosedClosed), // -180 will not be used $rando->getFloat(-180, 180, \Random\IntervalBoundary::OpenClosed), );
$rando = new Random\Randomizer();
printf(
"Lat: %+.6f Long: %+.6f",
$rando->getFloat(-90, 90, \Random\IntervalBoundary::ClosedClosed),
// -180 will not be used 
$rando->getFloat(-180, 180, \Random\IntervalBoundary::OpenClosed),
);

新的 nextFloat() 方法與使用 getFloat() 方法請求範圍從 0 到小於 1 的隨機值基本相同:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$rando = new Random\Randomizer();
$rando->nextFloat(); // 0.3767414902847
$rando = new Random\Randomizer(); $rando->nextFloat(); // 0.3767414902847
$rando = new Random\Randomizer();
$rando->nextFloat(); // 0.3767414902847

PHP 8.3 中的其他小改動

PHP 8.3 還包括許多其他新功能和小改動。我們將在下文中提及它們,並提供其他資源的連結(如有):

PHP 8.3 中的棄用

在 PHP 的每個新版本中,都會有一些函式和設定被標記為最終將被刪除。這些功能一旦被棄用,就不建議繼續使用,當它們出現在執行程式碼中時,會在許多日誌中生成通知。

下面是 PHP 8.3 中的棄用功能列表,並附有其他資訊的連結:

  • U_MULTIPLE_DECIMAL_SEPERATORS 常量已被棄用,改為 U_MULTIPLE_DECIMAL_SEPARATORS。
  •  3MT_RAND_PHP Mt19937 變體已被棄用。
  •  ReflectionClass::getStaticProperties() 不再為空。
  •  assert.activeassert.bailassert.callbackassert.exception, 和 assert.warning 等 INI 設定已被棄用。
  • 不帶引數呼叫 get_class()get_parent_class() 已被棄用。

小結

我們檢視了 PHP 8.3 中的重大變化。要檢視該版本中每項更新的詳細列表,請查閱該版本的官方更新日誌。如果您計劃將程式碼轉移到執行最新 PHP 的平臺上,《8.2 到 8.3 遷移指南》可能會幫您避免麻煩。

如果你負責在開發或生產伺服器上安裝 PHP,8.3 現在就可以下載了。

評論留言