首頁  >  文章  >  後端開發  >  PHP8.1新特性大講解之readonly properties唯讀屬性

PHP8.1新特性大講解之readonly properties唯讀屬性

藏色散人
藏色散人原創
2021-11-10 15:14:473557瀏覽

本文系翻譯,原文網址:https://stitcher.io/blog/php-81-readonly-properties

PHP 8.1:唯讀屬性

多年來,用PHP 編寫資料傳輸物件和值物件變得非常容易。以PHP 5.6 中的DTO 為例:

class BlogData
{
    /** @var string */
    private $title;
    
    /** @var Status */
    private $status;
    
    /** @var \DateTimeImmutable|null */
    private $publishedAt;
   
   /**
    * @param string $title 
    * @param Status $status 
    * @param \DateTimeImmutable|null $publishedAt 
    */
    public function __construct(
        $title,
        $status,
        $publishedAt = null
    ) {
        $this->title = $title;
        $this->status = $status;
        $this->publishedAt = $publishedAt;
    }
    
    /**
     * @return string 
     */
    public function getTitle()
    {
        return $this->title;    
    }
    
    /**
     * @return Status 
     */
    public function getStatus() 
    {
        return $this->status;    
    }
    
    /**
     * @return \DateTimeImmutable|null 
     */
    public function getPublishedAt() 
    {
        return $this->publishedAt;    
    }
}

並將其與PHP 8.0的等價物進行比較:

class BlogData
{
    public function __construct(
        private string $title,
        private Status $status,
        private ?DateTimeImmutable $publishedAt = null,
    ) {}
    
    public function getTitle(): string
    {
        return $this->title;    
    }
    
    public function getStatus(): Status 
    {
        return $this->status;    
    }
    
    public function getPublishedAt(): ?DateTimeImmutable
    {
        return $this->publishedAt;    
    }
}

這已經很不一樣了,儘管我認為仍然存在一個大問題:所有這些吸氣劑。就我個人而言,自從 PHP 8.0 及其提升的屬性以來,我不再使用它們。我只是更喜歡使用公共屬性而不是添加 getter:

class BlogData
{
    public function __construct(
        public string $title,
        public Status $status,
        public ?DateTimeImmutable $publishedAt = null,
    ) {}
}

物件導向的純粹主義者不喜歡這種方法:物件的內部狀態不應該直接暴露,並且絕對不能從外部改變。

在我們在Spatie 的專案中,我們有一個內部風格指南規則,即不應從外部更改具有公共屬性的DTO 和VO;一種似乎效果很好的做法,我們已經做了很長一段時間了,沒有遇到任何問題。

然而,是的;我同意如果語言確保公共屬性根本不會被覆蓋會更好。好吧,PHP 8.1透過引入readonly關鍵字解決了所有這些問題:

class BlogData
{
    public function __construct(
        public readonly string $title,
        public readonly Status $status,
        public readonly ?DateTimeImmutable $publishedAt = null,
    ) {}
}

這個關鍵字基本上就像它的名字所暗示的那樣:一旦設定了一個屬性,它就不能再被覆蓋:

$blog = new BlogData(
    title: 'PHP 8.1: readonly properties', 
    status: Status::PUBLISHED, 
    publishedAt: now()
);
$blog->title = 'Another title';
Error: Cannot modify readonly property BlogData::$title

知道當一個物件被建構時,它不會再改變,在編寫程式碼時提供了一定程度的確定性和平靜:一系列不可預見的資料變更根本不會再發生。

當然,您仍然希望能夠將資料複製到新對象,並可能在此過程中更改某些屬性。我們將在本文後面討論如何使用唯讀屬性來做到這一點。首先,讓我們深入了解它們。

您想要了解更多關於 PHP 8.1 的資訊嗎?有通往 PHP 8.1 的道路。在接下來的 10 天內,您將每天收到一封電子郵件,內容涉及 PHP 8.1 的一個新的和現有的功能;之後您將自動退訂,因此不會收到垃圾郵件或後續郵件。現在訂閱!

#僅輸入屬性

只讀屬性只能與類型化屬性結合使用:

class BlogData
{
    public readonly string $title;
    
    public readonly $mixed;
}

但是,您可以將其mixed用作類型提示:

class BlogData
{
    public readonly string $title;
    
    public readonly mixed $mixed;
}

這種限制的原因是透過省略屬性類型,null如果在建構函式中沒有提供明確值,PHP 會自動設定屬性的值。這種行為與 readonly 結合,會導致不必要的混亂。

#普通屬性和提升屬性

您已經看到了兩者的範例:readonly可以在普通屬性和提升屬性上新增:

class BlogData
{
    public readonly string $title;
    
    public function __construct(
        public readonly Status $status, 
    ) {}
}

#無預設值

只讀屬性不能有預設值:

class BlogData
{
    public readonly string $title = 'Readonly properties';
}

也就是說,除非它們是提升的屬性:

class BlogData
{
    public function __construct(
        public readonly string $title = 'Readonly properties', 
    ) {}
}

它之所以被允許提升屬性,是因為提升屬性的預設值不作為類別屬性的預設值,但只適用於建構函數的參數。在幕後,上面的程式碼將轉換為:

class BlogData
{
    public readonly string $title;
    
    public function __construct(
        string $title = 'Readonly properties', 
    ) {
        $this->title = $title;
    }
}

您可以看到實際屬性如何沒有被指派預設值。順便說一下,不允許只讀屬性的預設值的原因是它們與該形式的常數沒有任何不同。

#遺產

繼承期間不允許更改readonly 標誌:

class Foo
{
    public readonly int $prop;
}
class Bar extends Foo
{
    public int $prop;
}

這條規則是雙向的:readonly在繼承過程中不允許添加或刪除標誌。

#不允許取消設定

一旦設定了唯讀屬性,您就不能更改它,甚至不能取消它:

$foo = new Foo('value');
unset($foo->prop);

#反射

有一個新方法,以及一個標誌。 ReflectionProperty::isReadOnly()ReflectionProperty::IS_READONLY

#克隆

因此,如果您無法變更唯讀屬性,並且無法取消設定它們,那麼您如何建立DTO 或VO 的副本並更改其某些資料?您不能使用clone它們,因為您將無法覆蓋其值。實際上有一個想法是clone with在未來添加一個允許這種行為的構造,但這並不能解決我們現在的問題。

好吧,如果您依靠一點反射魔法,您可以複製具有更改的唯讀屬性的物件。透過建立一個物件而不呼叫它的建構函式(這可以使用反射),然後透過手動複製每個屬性——有時覆蓋它的值——你實際上可以「複製」一個物件並更改其唯讀屬性。

我做了一個小包來做到這一點,它是這樣的:

class BlogData
{
    use Cloneable;
    public function __construct(
        public readonly string $title,
    ) {}
}
$dataA = new BlogData('Title');
$dataB = $dataA->with(title: 'Another title');

我實際上寫了一篇專門的博客文章,解釋了所有這些背後的機制,你可以在這裡閱讀。

所以,這就是關於只讀屬性的全部內容。如果您正在處理處理大量 DTO 和 VO 的項目,並且需要您仔細管理整個程式碼中的資料流,我認為它們是一個很棒的功能。具有唯讀屬性的不可變物件在這方面有很大幫助。

以上是PHP8.1新特性大講解之readonly properties唯讀屬性的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn