PHP 屬性掛鉤

    php 屬性掛鉤

    介紹

    php 8.4 將于 2024 年 11 月發布,并將帶來一個很酷的新功能:屬性掛鉤。

    在本文中,我們將了解什么是屬性掛鉤以及如何在 php 8.4 項目中使用它們。

    順便說一句,您可能還有興趣查看我的另一篇文章,其中向您展示了 php 8.4 中添加的新數組函數。

    什么是 php 屬性掛鉤?

    屬性掛鉤允許您為類屬性定義自定義 getter 和 setter 邏輯,而無需編寫單獨的 getter 和 setter 方法。這意味著您可以直接在屬性聲明中定義邏輯,這樣您就可以直接訪問屬性(例如 $user->firstname),而不必記住調用方法(例如 $user->getfirstname() 和 $user->setfirstname()) .

    立即學習“PHP免費學習筆記(深入)”;

    您可以在 https://wiki.php.net/rfc/property-hooks 查看此功能的 rfc

    如果您是 laravel 開發人員,當您閱讀本文時,您可能會注意到鉤子看起來與 laravel 模型中的訪問器和修改器非常相似。

    我非常喜歡屬性掛鉤功能的外觀,我想當 php 8.4 發布時我將在我的項目中使用它。

    要了解屬性掛鉤的工作原理,讓我們看一些示例用法。

    “獲取”鉤子

    您可以定義一個 get 鉤子,每當您嘗試訪問屬性時都會調用該鉤子。

    例如,假設您有一個簡單的 user 類,它在構造函數中接受名字和姓氏。您可能想要定義一個 fullname 屬性,將名字和姓氏連接在一起。為此,您可以為 fullname 屬性定義一個 get 掛鉤:

    readonly class user
    {
        public string $fullname {
            get {
                return $this->firstname.' '.$this->lastname;
            }
        }
    
        public function __construct(
            public readonly string $firstname,
            public readonly string $lastname
        ) {
            //
        }
    }
    
    $user = new user(firstname: 'ash', lastname: 'allen');
    
    echo $user->firstname; // ash
    echo $user->lastname; // allen
    echo $user->fullname; // ash allen
    
    關注:愛掏網

    在上面的示例中,我們可以看到我們為 fullname 屬性定義了一個 get 鉤子,該鉤子返回一個通過將firstname和lastname屬性連接在一起計算得出的值。我們也可以使用類似于箭頭函數的語法來進一步清理它:

    readonly class user
    {
        public string $fullname {
            get =>  $this->firstname.' '.$this->lastname;
        }
    
        public function __construct(
            public readonly string $firstname,
            public readonly string $lastname,
        ) {
            //
        }
    }
    
    $user = new user(firstname: 'ash', lastname: 'allen');
    
    echo $user->firstname; // ash
    echo $user->lastname; // allen
    echo $user->fullname; // ash allen
    
    關注:愛掏網

    類型兼容性

    需要注意的是,getter 的返回值必須與屬性的類型兼容。

    如果未啟用嚴格類型,則該值將根據屬性類型進行類型轉換。例如,如果從聲明為字符串的屬性返回整數,則該整數將轉換為字符串:

    declare(strict_types=1);
    
    class user
    {
        public string $fullname {
            get {
                return 123;
            }
        }
    
        public function __construct(
            public readonly string $firstname,
            public readonly string $lastname,
        ) {
            //
        }
    }
    
    $user = new user(firstname: 'ash', lastname: 'allen');
    
    echo $user->fullname; // "123"
    
    關注:愛掏網

    在上面的例子中,即使我們指定了 123 作為要返回的整數,但“123”還是以字符串形式返回,因為該屬性是字符串。

    我們可以添加declare(strict_types=1);像這樣添加到代碼頂部以啟用嚴格的類型檢查:

    declare(strict_types=1);
    
    class user
    {
        public string $fullname {
            get {
                return 123;
            }
        }
    
        public function __construct(
            public readonly string $firstname,
            public readonly string $lastname,
        ) {
            //
        }
    }
    
    關注:愛掏網

    現在這會導致拋出錯誤,因為返回值是整數,但屬性是字符串:

    fatal error: uncaught typeerror: user::$fullname::get(): return value must be of type string, int returned
    
    關注:愛掏網

    “設置”鉤子

    php 8.4 屬性鉤子還允許您定義集合鉤子。每當您嘗試設置屬性時都會調用此函數。

    您可以為 set hook 在兩種單獨的語法之間進行選擇:

    • 顯式定義要在屬性上設置的值
    • 使用箭頭函數返回要在屬性上設置的值

    讓我們看看這兩種方法。我們想象一下,當在 user 類上設置名字和姓氏的首字母時,我們想要將它們設置為大寫:

    declare(strict_types=1);
    
    class user
    {   
        public string $firstname {
            // explicitly set the property value
            set(string $name) {
                $this->firstname = ucfirst($name);
            }
        }
    
        public string $lastname {
            // use an arrow function and return the value
            // you want to set on the property 
            set(string $name) => ucfirst($name);
        }
    
        public function __construct(
            string $firstname,
            string $lastname
        ) {
            $this->firstname = $firstname;
            $this->lastname = $lastname;
        }
    }
    
    $user = new user(firstname: 'ash', lastname: 'allen');
    
    echo $user->firstname; // ash
    echo $user->lastname; // allen
    
    關注:愛掏網

    正如我們在上面的示例中所看到的,我們為firstname 屬性定義了一個set hook,在將名稱設置為屬性之前,該鉤子將名稱的第一個字母大寫。我們還為 lastname 屬性定義了一個 set hook,它使用箭頭函數返回要在屬性上設置的值。

    類型兼容性

    如果屬性有類型聲明,那么它的 set hook 也必須有兼容的類型集。下面的示例將返回錯誤,因為 firstname 的 set hook 沒有類型聲明,但屬性本身有 string 的類型聲明:

    class user
    {   
        public string $firstname {
            set($name) => ucfirst($name);
        }
    
        public string $lastname {
            set(string $name) => ucfirst($name);
        }
    
        public function __construct(
            string $firstname,
            string $lastname
        ) {
            $this->firstname = $firstname;
            $this->lastname = $lastname;
        }
    }
    
    關注:愛掏網

    嘗試運行上面的代碼將導致拋出以下錯誤:

    fatal error: type of parameter $name of hook user::$firstname::set must be compatible with property type
    
    關注:愛掏網

    一起使用“get”和“set”鉤子

    您不限于單獨使用 get 和 set 掛鉤。您可以在同一房產中一起使用它們。

    舉個簡單的例子。我們假設我們的 user 類有一個 fullname 屬性。當我們設置屬性時,我們會將全名分為名字和姓氏。我知道這是一種幼稚的方法,并且有更好的解決方案,但這純粹是為了舉例來突出顯示掛鉤屬性。

    代碼可能看起來像這樣:

    declare(strict_types=1);
    
    class user
    {
        public string $fullname {
            // dynamically build up the full name from
            // the first and last name
            get => $this->firstname.' '.$this->lastname;
    
            // split the full name into first and last name and
            // then set them on their respective properties
            set(string $name) {
                $splitname = explode(' ', $name);
                $this->firstname = $splitname[0];
                $this->lastname = $splitname[1];
            }
        }
    
        public string $firstname {
            set(string $name) => $this->firstname = ucfirst($name);
        }
    
        public string $lastname {
            set(string $name) => $this->lastname = ucfirst($name);
        }
    
        public function __construct(string $fullname) {
            $this->fullname = $fullname;
        }
    }
    
    $user = new user(fullname: 'ash allen');
    
    echo $user->firstname; // ash
    echo $user->lastname; // allen
    echo $user->fullname; // ash allen
    
    關注:愛掏網

    在上面的代碼中,我們定義了一個 fullname 屬性,它同時具有 get 和 set 鉤子。 get 掛鉤通過將名字和姓氏連接在一起來返回全名。 set 鉤子將全名拆分為名字和姓氏,并將它們設置在各自的屬性上。

    您可能還注意到,我們沒有為 fullname 屬性本身設置值。相反,如果我們需要讀取 fullname 屬性的值,則會調用 get 掛鉤以根據名字和姓氏屬性構建全名。我這樣做是為了強調,您可以擁有一個不直接設置值的屬性,而是根據其他屬性計算該值。

    在升級屬性上使用屬性掛鉤

    屬性掛鉤的一個很酷的功能是您還可以將它們與構造函數提升的屬性一起使用。

    讓我們看一個不使用提升屬性的類的示例,然后看看使用提升屬性時它會是什么樣子。

    我們的用戶類可能看起來像這樣:

    readonly class user
    {
        public string $fullname {
            get => $this->firstname.' '.$this->lastname;
        }
    
        public string $firstname {
            set(string $name) => ucfirst($name);
        } 
    
        public string $lastname {
            set(string $name) => ucfirst($name);
        }
    
        public function __construct(
            string $firstname,
            string $lastname,
        ) {
            $this->firstname = $firstname;
            $this->lastname = $lastname;
        }
    }
    
    關注:愛掏網

    我們可以將firstname和lastname屬性提升到構造函數中,并直接在屬性上定義它們的設置邏輯:

    readonly class user
    {
        public string $fullname {
            get => $this->firstname.' '.$this->lastname;
        }
    
        public function __construct(
            public string $firstname {
                set (string $name) => ucfirst($name);
            }, 
            public string $lastname {
                set (string $name) => ucfirst($name);
            }
        ) {
            //
        }
    }  
    
    關注:愛掏網

    只寫掛鉤屬性

    如果您使用 setter 定義了一個掛鉤屬性,但實際上并未在該屬性上設置值,則該屬性將是只寫的。這意味著你無法讀取屬性的值,只能設置它。

    讓我們采用前面示例中的 user 類,并通過刪除 get 掛鉤將 fullname 屬性修改為只寫:

    declare(strict_types=1);
    
    class user
    {
        public string $fullname {
            // define a setter that doesn't set a value
            // on the "fullname" property. this will
            // make it a write-only property.
            set(string $name) {
                $splitname = explode(' ', $name);
                $this->firstname = $splitname[0];
                $this->lastname = $splitname[1];
            }
        }
    
        public string $firstname {
            set(string $name) => $this->firstname = ucfirst($name);
        }
    
        public string $lastname {
            set(string $name) => $this->lastname = ucfirst($name);
        }
    
        public function __construct(
            string $fullname,
        ) {
            $this->fullname = $fullname;
        }
    }
    
    $user = new user('ash allen');
    
    echo $user->fullname; // will trigger an error!
    
    關注:愛掏網

    如果我們運行上面的代碼,我們會在嘗試訪問 fullname 屬性時看到拋出以下錯誤:

    fatal error: uncaught error: property user::$fullname is write-only
    
    關注:愛掏網

    只讀掛鉤屬性

    同樣,屬性也可以是只讀的。

    例如,假設我們只希望從firstname 和lastname 屬性生成fullname 屬性。我們不想允許直接設置 fullname 屬性。我們可以通過從 fullname 屬性中刪除 set 鉤子來實現這一點:

    class user
    {
        public string $fullname {
            get {
                return $this->firstname.' '.$this->lastname;
            }
        }
    
        public function __construct(
            public readonly string $firstname,
            public readonly string $lastname,
        ) {
            $this->fullname = 'invalid'; // will trigger an error!
        }
    }
    
    關注:愛掏網

    如果我們嘗試運行上面的代碼,則會拋出以下錯誤,因為我們試圖直接設置 fullname 屬性:

    uncaught error: property user::$fullname is read-only
    
    關注:愛掏網

    使用“readonly”關鍵字

    即使我們的 php 類具有掛鉤屬性,您仍然可以將它們設置為只讀。例如,我們可能想讓 user 類只讀:

    readonly class user
    {   
        public string $firstname {
            set(string $name) => ucfirst($name);
        }
    
        public string $lastname {
            set(string $name) => ucfirst($name);
        }
    
        public function __construct(
            string $firstname,
            string $lastname,
        ) {
            $this->firstname = $firstname;
            $this->lastname = $lastname;
        }
    }
    
    關注:愛掏網

    但是,hook 屬性不能直接使用 readonly 關鍵字。例如,這個類將是無效的:

    class user
    {
        public readonly string $fullname {
            get => $this->firstname.' '.$this->lastname;
        }
    
        public function __construct(
            string $firstname,
            string $lastname,
        ) {
            $this->firstname = $firstname;
            $this->lastname = $lastname;
        }
    }
    
    關注:愛掏網

    上面的代碼會拋出以下錯誤:

    fatal error: hooked properties cannot be readonly
    
    關注:愛掏網

    property”魔法常數

    在 php 8.4 中,引入了一個名為 __property__ 的新魔法常量。該常量可用于引用屬性掛鉤內的屬性名稱。

    讓我們看一個例子:

    class user
    {
        // ...
    
        public string $lastname {
            set(string $name) {
                echo __property__; // lastname
                $this->{__property__} = ucfirst($name); // will trigger an error!
            }
        }
    
        public function __construct(
            string $firstname,
            string $lastname,
        ) {
            $this->firstname = $firstname;
            $this->lastname = $lastname;
        }
    }
    
    關注:愛掏網

    在上面的代碼中,我們可以看到在lastname屬性的setter中使用__property__將會輸出屬性名稱lastname。然而,還值得注意的是,嘗試使用此常量來嘗試設置屬性值將觸發錯誤:

    fatal error: uncaught error: must not write to virtual property user::$lastname
    
    關注:愛掏網

    有一個關于 __property__ 魔法常量的方便用例示例,您可以在 github 上查看:https://github.com/crell/php-rfcs/blob/master/property-hooks/examples.md。

    接口中的掛鉤屬性

    php 8.4 還允許您在接口中定義可公開訪問的掛鉤屬性。如果您想強制類使用鉤子實現某些屬性,這會很有用。

    讓我們看一下聲明了掛鉤屬性的示例接口:

    interface nameable
    {
        // expects a public gettable 'fullname' property
        public string $fullname { get; }
    
        // expects a public gettable 'firstname' property
        public string $firstname { get; }
    
        // expects a public settable 'lastname' property
        public string $lastname { set; }
    }
    
    關注:愛掏網

    在上面的接口中,我們定義任何實現 nameable 接口的類都必須具有:

    • 至少可公開獲取的 fullname 屬性。這可以通過定義 get hook 或根本不定義 hook 來實現。
    • 至少可公開獲取的firstname 屬性。
    • 至少可公開設置的姓氏屬性。這可以通過定義具有設置鉤子的屬性或根本不定義鉤子來實現。但如果該類是只讀的,那么該屬性必須有一個設置的鉤子。

    這個實現 nameable 接口的類是有效的:

    class user implements nameable
    {
        public string $fullname {
            get => $this->firstname.' '.$this->lastname;
        }
    
        public string $firstname {
            set(string $name) => ucfirst($name);
        }
    
        public string $lastname;
    
        public function __construct(
            string $firstname,
            string $lastname,
        ) {
            $this->firstname = $firstname;
            $this->lastname = $lastname;
        }
    }
    
    關注:愛掏網

    上面的類是有效的,因為 fullname 屬性有一個 get 鉤子來匹配接口定義。 firstname 屬性只有一個 set hook,但仍然可以公開訪問,因此它滿足條件。 lastname 屬性沒有 get 掛鉤,但它是可公開設置的,因此它滿足條件。

    讓我們更新 user 類以強制執行 fullname 屬性的 get 和 set 掛鉤:

    interface nameable
    {
        public string $fullname { get; set; }
    
        public string $firstname { get; }
    
        public string $lastname { set; }
    }
    
    關注:愛掏網

    我們的 user 類將不再滿足 fullname 屬性的條件,因為它沒有定義 set hook。這會導致拋出以下錯誤:

    fatal error: class user contains 1 abstract methods and must therefore be declared abstract or implement the remaining methods (nameable::$fullname::set)
    
    關注:愛掏網

    抽象類中的掛鉤屬性

    與接口類似,你也可以在抽象類中定義鉤子屬性。如果您想提供一個定義子類必須實現的掛鉤屬性的基類,這可能很有用。您還可以在抽象類中定義鉤子,并在子類中覆蓋它們。

    例如,讓我們創建一個 model 抽象類,定義一個必須由子類實現的 name 屬性:

    abstract class model
    {
        abstract public string $fullname {
            get => $this->firstname.' '.$this->lastname;
            set;
        }
    
        abstract public string $firstname { get; }
    
        abstract public string $lastname { set; }
    }
    
    關注:愛掏網

    在上面的抽象類中,我們定義任何擴展 model 類的類都必須具有:

    • 至少可公開獲取和設置的 fullname 屬性。這可以通過定義 get 和 set 鉤子或根本不定義鉤子來實現。我們還在抽象類中定義了 fullname 屬性的 get 鉤子,因此我們不需要在子類中定義它,但如果需要,可以覆蓋它。
    • 至少可公開獲取的firstname 屬性。這可以通過定義 get hook 或根本不定義 hook 來實現。
    • 至少可公開設置的姓氏屬性。這可以通過定義具有設置鉤子的屬性或根本不定義鉤子來實現。但如果該類是只讀的,那么該屬性必須有一個設置的鉤子。

    然后我們可以創建一個擴展 model 類的 user 類:

    class User extends Model
    {
        public string $fullName;
    
        public string $firstName {
            set(string $name) => ucfirst($name);
        }
    
        public string $lastName;
    
        public function __construct(
            string $firstName,
            string $lastName,
        ) {
            $this->firstName = $firstName;
            $this->lastName = $lastName;
        }
    }
    
    關注:愛掏網

    結論

    希望本文能讓您深入了解 php 8.4 屬性掛鉤的工作原理以及如何在 php 項目中使用它們。

    如果這個功能一開始看起來有點令人困惑,我也不會太擔心。當我第一次看到它時,我也有點困惑(特別是它們如何與接口和抽象類一起工作)。但一旦你開始修補它們,你很快就會掌握它的竅門。

    我很高興看到這個功能將如何在野外使用,我期待著 php 8.4 發布時在我的項目中使用它。

    如果您喜歡閱讀這篇文章,您可能有興趣查看我的 220 多頁電子書“battle ready laravel”,其中更深入地涵蓋了類似的主題。

    或者,您可能想查看我的另一本 440 多頁電子書“consuming apis in laravel”,它教您如何使用 laravel 來使用來自其他服務的 api。

    如果您有興趣在我每次發布新帖子時獲得更新,請隨時訂閱我的時事通訊。

    繼續創造精彩的東西! ?

    以上就是PHP 屬性掛鉤的詳細內容,更多請關注愛掏網 - it200.com其它相關文章!

    聲明:所有內容來自互聯網搜索結果,不保證100%準確性,僅供參考。如若本站內容侵犯了原著者的合法權益,可聯系我們進行處理。
    發表評論
    更多 網友評論0 條評論)
    暫無評論

    返回頂部

    主站蜘蛛池模板: 国精产品一区一区三区| 亚洲天堂一区二区三区四区| 亚洲Av无码国产一区二区| 波多野结衣精品一区二区三区 | 在线精品日韩一区二区三区| 国产精品视频一区二区三区四| 精品一区二区三区水蜜桃| 中文字幕一区日韩精品| 亚洲国产一区在线| 国产精品高清一区二区三区| 动漫精品专区一区二区三区不卡| 久久精品无码一区二区三区免费 | 精品无码一区二区三区在线| 东京热人妻无码一区二区av| 无码人妻精品一区二| 爱爱帝国亚洲一区二区三区| 国产品无码一区二区三区在线| 亚欧在线精品免费观看一区| 久久久久人妻精品一区二区三区| 国产精品丝袜一区二区三区 | 亚洲国产专区一区| www亚洲精品少妇裸乳一区二区| 国产亚洲综合一区二区三区| 国产在线观看91精品一区| 国产伦精品一区二区三区四区 | 精品人妻系列无码一区二区三区| 一区二区三区观看免费中文视频在线播放 | 亚洲片国产一区一级在线观看| 国产精品一区二区三区久久| 国产一区二区三区电影| 一区二区福利视频| 久久se精品动漫一区二区三区| 国产爆乳无码一区二区麻豆 | 亚洲国产精品一区二区九九 | 亚洲狠狠狠一区二区三区| 国产精品电影一区| 国产精品一区二区四区| 亚洲第一区精品观看| 日本一区二区三区精品中文字幕| 中文字幕一区二区三区精彩视频| 无码日本电影一区二区网站|