2023年1月31日

PHP 8.2 發布,動態屬性要被廢棄了!迎來更完整的型別系統,新增唯讀類別

2022 年 12 月 8 號 PHP 8.2 發布了,這是 PHP 8 的第二次重大更新。從 2020 年開始,PHP 維持著每年更新一個次版本的進度,2020 年 11 月的 8.0,2021 年 11 月推出了 8.1。

作為最熱門的 PHP 框架,Laravel 9 及 10 都支援 PHP 8.2,如果還在使用 Laravel 8 及以前的版本,最多只支援到 8.1。

那我們話不多說,趕緊來看一下 PHP 8.2 有哪些重大的調整吧。

PHP 8.2 重大變動

Readonly classes 唯讀類別

PHP 8.1 的時候,PHP 推出了唯讀的屬性,而 8.2 則進化成唯讀類別。

一個類別被標記為唯讀時,它的所有屬性都會自動變成唯讀屬性 (Readonly Properties),而該類別將沒辦法動態地新增屬性。

readonly class Book
{
    public string $title;

    public function __construct(string $title)
    {
        $this->title = $title;
    }
}

$book = new Book('Clean Code');
$book->title = 'Refactor';

會跳出錯誤訊息:

 Cannot modify readonly property Book::$title 

如果我們想要動態新增屬性也會被阻擋,例如我們動態的指派 author 到範例的 book 物件中:

$book->author = 'Robert C. Martin';

錯誤訊息:

Cannot create dynamic property Book::$author

DNF Types

DNF,全名 Disjunctive Normal Form,中文有點拗口我就不硬翻了。總之就是一種正規形式,用來將許多交集(AND)做聯集(OR),也就是 OR of ANDs

範例:

  • (A&B)|C
  • A|(B&C)|D
  • (A&B)|(C&D)

在 8.0 的時候,PHP 新增了聯集 Union Type,在 8.1 則新增了交集 Intersection Type。支援 DNF 後,在定義 PHP 的 function 參數型別,有了更多的彈性。

這項更新有助於我們在定義 function 時,更精準地宣告傳入的型別,或回傳的型別(return type)。

unit type 單值型別

PHP 8.2 可將 nullfalsetrue 分別視為獨立的型別。

過去在 PHP 8.0 中,null 雖然可以作為 union type 的一部分,但卻沒辦法單獨存在。

至於 truefalse,過去只能使用 bool來表示型別,但有些 function 雖然回傳型別宣告成 bool, 但它其實並不回傳 true, 只會回傳 false,這種時候使用 bool 型別就不夠精準。

因此 PHP 8.2 允許 nullfalsetrue 作為一個獨立的型別。這些型別因為只包含一種值,所以稱為 unit type

官網範例:

class Falsy  
{  
  public function alwaysFalse(): false {}   
  public function alwaysTrue(): true {}   
  public function alwaysNull(): null {}  
}
unit type 是 PHP 8.0 新增的功能,可以將兩個不同的類別聯合起來,作為一個參數的型別。例如 function foo(string|int $boo),代表這個 $boo可以是字串也可以是數字。

複習 8.0 版的更新→這邊

在 traits 中的常數

一個實用且簡單的新功能,可以在 Trait 中宣告常數。

官網範例如下。不過要特別注意 Trait 的常數沒辦法直接被讀取,必須透過使用它的類別來做使用。

trait Foo  
{  
  public const CONSTANT = 1;  
}  
  
class Bar 
{  
  use Foo;  
}  
  
var_dump(Bar::CONSTANT); // 1  
var_dump(Foo::CONSTANT); // Error

棄用動態屬性

PHP 中非常方便但也非常容易造成糞 Code 的動態屬性終於要被拿掉了。

在 8.2 中,使用動態屬性會有警告,根據 RFC 文件,可能在 PHP 9 會正式移除,屆時使用動態屬性則會拋出錯誤。但魔術方法 __get__set 目前並不受這個改變影響。

class Book  
{  
  public $title;  
}  
  
$book = new Book();  
$book->author = 'Robert C. Martin'; 
// Deprecated notice

小結論

可以觀察到,相較於 8.0 及 8.1,PHP 8.2 的變動其實沒有很多,但也可以感受到 PHP 8.2 很努力在完善 PHP 的型別系統,讓 PHP 程式可以更穩健更可靠。

而棄用動態屬性也是一個很大的壯舉,可以終結動態屬性的濫用,讓 PHP 程式更不容易出錯,也更好除錯。

不過這讓我想到 Laravel,它在 Model 大量地使用了動態屬性,雖然 Laravel 都是透過魔術方法 __get__set 來完成,雖然魔術方法並不在這次調整的影響範圍,但是不是也代表未來 Laravel 會改變設計的邏輯?這點值得觀察。

複習 8.1 更新了什麼→這邊

參考連結

PHP 8.2 Released!

更多關於 null type 的討論