2023 年底,PHP 8.3.0 版本釋出 (🐕 也為了衝業績

Tacks發表於2023-11-25

PHP 8.3

  • Title: 2023 年底,PHP 8.3.0 版本釋出
  • Tag: PHP
  • Author: Tacks
  • Create-Date: 2023-11-25
  • Update-Date: 2023-11-25

Ref

Hello PHP 8.3.0

在2023年11月23日, 恭喜 PHP 8.3.0 新的大版本重磅釋出 !

PHP8.3 ,新增 JSON有效性檢查函式類常量顯式型別只讀屬性深複製,當然還有一些效能最佳化、錯誤修復,常規清理等。

它對只讀類、新函式、最近新增的類的補充Randomizer、堆疊溢位檢測等進行了改進。

捋一下 PHP 更新節奏

從 php8.0.0 從 2020 釋出開始,基本上每年更新一個版本,直到現在的 8.3 ,希望保持這個節奏啊,如果可以預計 2025 能看到 php9 ?

  • 📌 26 Nov 2020 🐘 php8.0.0
  • 📌 23 Nov 2021 💰 THE PHP Foundation PHP 基金會成立
  • 📌 25 Nov 2021 🐘 php8.1.0
  • 📌 08 Dec 2022 🐘 php8.2.0
  • 📌 23 Nov 2023 🐘 php8.3.0

從時間是來看,今年比以往來得更早一些,提前完成 KPI,加油 PHPer 💞

個人對本次php8.3更新的一些小理解

  • 💥💥💥💥💥 日常使用方法最相關
  • ⭐⭐⭐⭐ 型別宣告之路逐步完善
  • ☔☔☔ 特定情況下解決方案
  • 💧💧 不痛不癢的更新
  • ⚡ 比較意外但貌似幫助也不大
  • ⛔ 打破了你之前的認識

新的功能

0x1. json_validate() Function (json校驗函式) 💥💥💥💥💥

過去&現在

  • before

驗證字串是否為有效 JSON 的唯一方法是對其進行 json_decode() 解碼並檢測是否丟擲任何錯誤;

  • now

千呼萬喚始出來, json_validate() 函式來了,它只判斷輸入是否是有效的 JSON,相比 json_decode() ,使用記憶體更少;

用法

json_validate(string $json, int $depth = 512, int $flags = 0): bool

/**
 * json 驗證 (php83之前)
 *
 * @param string $string
 * @return boolean|string
 */
function jsonValidate(string $string): bool|string {
    $data = json_decode($string);
    if($data === null || json_last_error() !== JSON_ERROR_NONE) {
        return json_last_error_msg();
    } 
    return true;
}


// php8.3
json_validate('{ "test": { "foo": "bar" } }'); // true
json_validate('{ "test": { "foo": "bar" , "test"} }'); // false

影響範圍

說真的,這並不會影響我們平時的開發,因為,99%的場景下我們本身就是需要用到 json 解碼後的資料來做操作,而不只是單純的驗證 json 字串。

so,只有一些小部分場景可能需要,如你只需要判斷使用者的 json 內容然後錄入系統中,通常如 日誌類json 等。

0x2. Typed class constants (類常量顯式型別) ⭐⭐⭐⭐

過去&現在

  • before

從 php8,就逐步佈局型別定義,雖然背後還是弱型別,但提供了型別宣告,就能讓編譯器/解析器,提前告訴你程式碼可能的問題,但類常量還沒有支援,這不,一步步完善

  • now

php8.3 ,增加了類常量的型別定義,如果你給了不符合型別的值,將會丟擲 Fatal error 致命錯誤,當然這不是必須的,你依然可以不參用型別宣告

用法

class BeforeFoo
{
    // php83 之前,可以直接這麼定義
    const BAR = 'bar'; 
}

class Foo
{
    // php83 時,可以給常量也加上型別宣告
    const string BAR = 'bar'; 
}

class ErrFoo
{
    // php83 時,你可以給常量加型別宣告,但如果錯誤,將會丟擲異常
    // Fatal error: Cannot use string as value for class constant ErrFoo::BAR of type int
    const int BAR = 'bar'; 
}

0x3. Dynamic class constant fetch (動態類常量獲取) 💧💧

過去&現在

  • before

php 貌似很喜歡玩動態型別獲取。比如 $$var 可變變數,放眼其他程式語言貌似都沒這麼玩的 ,順便看看 到底有哪些動態獲取成員名稱的手段

// 可變變數
$$foo;
// 靜態屬性
Foo::${$bar};
// 靜態屬性
$obj::$bar;
// 成員屬性
$obj->$foo;
// 靜態方法
Foo::{$bar}();
// 靜態方法
$obj::bar();
// 方法
$foo->{$bar}();
  • now

看到 類常量動態獲取的方式 還沒引入,php83 那就引入吧

// 類常量
Foo::{$name};

用法

class Foo 
{
    const BAR = 'bar';
    public $name = '1';
}
$name = 'BAR';

// php8.3 之前
echo constant(Foo::class . '::' . $name) , PHP_EOL;

// php8.3 現在
echo Foo::{$name} , PHP_EOL;

// php8.3 現在 切記給變數帶上 {} ,它標識先解析 name 變數,再獲取類常量
// 下面是錯誤用法:Fatal error: Uncaught Error: Access to undeclared static property Foo::$name
// echo Foo::$name;

更多

class MyClass {
    const VAR1 = 'class constants';
    public static $var2 = 'class static property';
    public $var3 = 'class property';

    public static function func1() { return 'class static function '. __FUNCTION__; }
    public function func2() { return 'class function '. __FUNCTION__; }

}

echo '【類常量-類-直接獲取】            |   MyClass::VAR1           | ',  MyClass::VAR1 , PHP_EOL;
echo '【類靜態屬性-類-直接獲取】        |   MyClass::$var2           | ',  MyClass::$var2 , PHP_EOL;
echo '【成員屬性-物件-直接獲取】        |   (new MyClass())->var3    | ',  (new MyClass())->var3 , PHP_EOL;

echo "================================================================================================", PHP_EOL;

$bar = 'VAR1';
echo '【類常量-類-動態獲取】        | bar = VAR1 | MyClass::{$bar};         | ', MyClass::{$bar} , PHP_EOL;

$bar = 'var2';
echo '【類靜態屬性-類-動態獲取】    | bar = var2 | MyClass::${$bar};         | ', MyClass::${$bar} , PHP_EOL;

$bar = 'var2';
echo '【類靜態屬性-物件-動態獲取】  | bar = var2 | (new MyClass)::${$bar};   | ', (new MyClass)::${$bar} , PHP_EOL;

$bar = 'var3';
echo '【成員屬性-物件-動態獲取】    | bar = var3 | (new MyClass)->${$bar};   | ', (new MyClass)->{$bar} , PHP_EOL;

$bar = 'func1';
echo '【靜態方法-類-動態獲取】      | bar =func1 | MyClass::{$bar}();        | ', MyClass::{$bar}() , PHP_EOL;
echo '【靜態方法-物件-動態獲取】    | bar =func1 | (new MyClass)::{$bar}();  | ', (new MyClass)::{$bar}() , PHP_EOL;

$bar = 'func2';
echo '【成員方法-物件-動態獲取】    | bar =func2 | (new MyClass)->{$bar}();  | ', (new MyClass)->{$bar}() , PHP_EOL;

// 別忘記列舉值
enum Status: string {
    case S_OK = 'ok';
}

$bar = 'S_OK';
echo '【列舉值-列舉-動態獲取】    | bar =S_OK | Status::{$bar}->value;  | ', Status::{$bar}->value , PHP_EOL;

0x4. Deep Clone readonly properties (只讀屬性深複製) ☔☔☔

用法

readonly class People
{
    public function __construct(
        public string $name,
        public string $age,
        public DateTime $createdAt,
    ) {}


    public function __clone()
    {
        $this->createdAt = new DateTime(); 
    }
}

$tacks = new People(
    name: 'Tacks',
    age: 18,
    createdAt: new DateTime(),
);

var_dump($tacks);

$nextMe = clone $tacks;

// php8.3 之前 Fatal error: Uncaught Error: Cannot modify readonly property People::$createdAt
// php8.3 可用 在克隆過程中可以重新初始化只讀屬性
var_dump($nextMe);

補充:匿名只讀類也在 php8.3支援了

// php8.3 之前 Parse error: syntax error, unexpected token "readonly"
// php8.3 可用
$other = new readonly class {
    public function __construct(
        public string $foo = 'bar',
    ) {}
};
var_dump($other->foo);

0x5. Marking overriden methods with new attribute (標記重寫方法 #[\Override]) ⭐⭐⭐⭐

過去&現在

  • before

使用 #[Override] 註解標記方法,表示您知道該方法正在重寫父方法,它唯一要做的就是表現出意圖,防止父類如果無意間被修改了方法名,將會導致子類的不可預見性錯誤。

  • now

但是現在,php8.3 有了 #[Override] 註解,讓 PHP 知道了子類的這個方法是覆蓋父類的,如果沒有則會丟擲錯誤,當然這也更加友好,因為如果你的 IDE 如果支援,或者有 靜態分析器可以提前檢查對應的錯誤。

Fatal error: MyClass::foo() has #[\Override] attribute, but no matching parent method exists

使用

abstract class Father
{
    public function foo(): int
    {
        return 1;
    }
}

final class MyClass extends Father
{
    #[Override]
    public function foo(): int
    {
        return 2; // The overridden method
    }
} 

0x6. New Randomizer::getFloat() and Randomizer::nextFloat() methods (Randomizer類新增兩個浮點數方法) 💥💥💥💥💥

由於浮點數的精度有限和隱式舍入,生成位於特定區間內的無偏浮點數並非易事,並且常用的使用者態解決方案可能會生成有偏差的結果或超出請求範圍的數字。

所以說官方出手了,還是比自己封裝的要高效好用,之後隨機浮點數可以來參考這個了。

# 獲取隨機浮點數,在 min,max ,第三個引數則是決定是否包含邊界
public Random\Randomizer::getFloat(float $min, float $max, Random\IntervalBoundary $boundary = Random\IntervalBoundary::ClosedOpen): float

# 獲取 (0,1) 之間的浮點數 
public Random\Randomizer::nextFloat(): float

使用

//  新增 getFloat() nextFloat()

$randomizer = new \Random\Randomizer();

// getFloat($min, $max) 返回之間的浮點數
// Closed表示包含該值,表示Open排除該值
$temperature = $randomizer->getFloat(
    -89.2,
    56.7,
    \Random\IntervalBoundary::ClosedClosed,
);
var_dump($temperature);

// nextFloat() 它將為您提供 0 到 1 之間的隨機浮點數,其中 1 被排除
var_dump($randomizer->nextFloat());

$chanceForTrue = 0.8;
$myBoolean = $randomizer->nextFloat() < $chanceForTrue;
var_dump($myBoolean);

0x7. Command line linter supports multiple files (支援 -l 檢查多個檔案) ⚡

過去&現在

說實話,用 php 也挺久的了,竟然沒發現,或者說沒用過 -l 選項。要不是這次看了 php8.3 的新功能首頁 ,把這個功能明顯的列出來,我還真不知道,哈哈哈哈哈。

  • before

php8.3 之前, php -l test.php 只支援單個檔案,及時你寫多個檔案,也是以第一個檔案為準

  • now

php8.3 時, php -l test1.php test2.php 支援多個檔案了,會按照順序依次檢查

使用

$ php -l test1.php test2.php

Parse error: syntax error, unexpected end of file in test1.php on line 21
Errors parsing test1.php

Parse error: syntax error, unexpected token "{", expecting identifier in test2.php on line 5
Errors parsing test2.php

0x8. Negative indices in arrays (陣列中的負數索引) ⛔

過去&現在

  • before

php8.3 之前, 第一個索引是負數,第二個預設還是 0 開始

  • now

php8.3 時,第一個索引是負數,第二個預設則是加一之後的索引,而不一定是 0

使用

$array = [];

$array[-9] = 'a';
$array[]   = 'b';

var_export($array);

/*

===> php8.3 之前
array (
  -9 => 'a',
  0 => 'b',
)


===> php8.3 時
array (
  -9 => 'a',
  -8 => 'b',
)
 */

0x9. New posix’s function (posix擴充套件相關函式的新增) 💧💧

0x10. Other

更多更新細節,可以看

堆疊溢位檢測

PHP8.3 新增了兩個新的 ini 指令

  • zend.max_allowed_stack_size
    • 0 意味著 PHP 將自動確定一個值
    • -1 表明沒有限制或特定的位元組數
    • 現有 fiber.stack_size指令用作允許的最大堆疊大小
  • zend.reserved_stack_size 用於確定“緩衝區”, 以便 PHP 仍然能夠丟擲錯誤而不是實際耗盡記憶體
    • 這裡的值應該是多個位元組,但 PHP 將為您確定一個合理的預設值,無需填寫

好處: Error 當使用超過 和 zend.max_allowed_stack_size 之間的差異時,接近溢位呼叫堆疊的程式現在可能會丟擲 zend.reserved_stack_size。此功能的好處是堆疊溢位引起的分段錯誤將不再導致段錯誤,從而使除錯變得更加容易。

新 mb_str_pad() 功能

在 PHP 中,各種字串函式有兩種變體:一種用於位元組字串,另一種用於多位元組字串。然而,多位元組字串函式中值得注意的一個缺陷 mbstringstr_pad()

str_pad()函式缺乏多位元組字元支援,因此在使用使用多位元組編碼(如 UTF-8)的語言時會出現問題。該 RFC 建議在 PHP 中新增這樣一個函式,我們將其稱為 mb_str_pad()

非RFC提交

並非 PHP 中的每個更改都會透過 RFC 流程。事實上,大多數更改包括維護和錯誤修復,並且不需要 RFC。所有這些更改都列在升級文件中。

  • 使用FFI時,返回型別為 void 現在返回 null 而不是 C 函式 FFI\CData:void
  • posix_getrlimit() 現在採用可選$res引數來允許獲取單個資源限制
  • gc_status() 有四個新欄位 running、protected、full 、buffer_size
  • class_alias()現在支援建立內部類的別名
  • array_pad() 現在僅受陣列可以擁有的最大元素數的限制。之前,一次最多隻能新增 1048576 個元素
  • 在 posix 系統上執行 proc_get_status() 多次將始終返回正確的值
  • opcache.consistency_checksini 指令已刪除
  • 改進 array_sum()array_product()
本作品採用《CC 協議》,轉載必須註明作者和本文連結
明天我們吃什麼 悲哀藏在現實中 Tacks

相關文章