PHP プロパティフック

PHPz
リリース: 2024-08-21 20:44:50
オリジナル
1051 人が閲覧しました

PHP  Property Hooks

導入

PHP 8.4 は 2024 年 11 月にリリースされ、優れた新機能であるプロパティ フックが導入される予定です。

この記事では、プロパティ フックとは何か、およびそれらを PHP 8.4 プロジェクトでどのように使用するかを見ていきます。

余談ですが、PHP 8.4 に追加される新しい配列関数を紹介する私の他の記事もチェックしていただければ幸いです。

PHP プロパティフックとは何ですか?

プロパティ フックを使用すると、個別のゲッター メソッドとセッター メソッドを作成することなく、クラス プロパティのカスタム ゲッター ロジックとセッター ロジックを定義できます。これは、プロパティ宣言でロジックを直接定義できるため、メソッド ($user->getFirstName() や $user- など) を呼び出すことを忘れずに、プロパティ ($user->firstName など) に直接アクセスできることを意味します。 >setFirstName()).

この機能の RFC は https://wiki.php.net/rfc/property-hooks で確認できます

あなたが Laravel 開発者であれば、この記事を読んでいるときに、フックが Laravel モデルのアクセサーやミューテーターと非常によく似ていることに気づくかもしれません。

私はプロパティ フック機能の見た目がとても気に入っているので、PHP 8.4 がリリースされたら自分のプロジェクトで使用することになると思います。

プロパティ フックがどのように機能するかを理解するために、使用例をいくつか見てみましょう。

「取得」フック

プロパティにアクセスしようとするたびに呼び出される get フックを定義できます。

たとえば、コンストラクターで firstName と lastName を受け入れる単純な 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
ログイン後にコピー

上記の例では、firstName プロパティと lastName プロパティを連結して計算された値を返す fullName プロパティの get フックを定義したことがわかります。アロー関数に似た構文を使用することで、これをもう少し整理することもできます。

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
ログイン後にコピー

タイプの互換性

ゲッターからの戻り値はプロパティの型と互換性がある必要があることに注意することが重要です。

厳密な型が有効になっていない場合、値はプロパティの型に合わせて型調整されます。たとえば、文字列として宣言されているプロパティから整数を返す場合、その整数は文字列に変換されます。

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 プロパティ フックでは、セット フックを定義することもできます。これは、プロパティを設定しようとするたびに呼び出されます。

セットフックには 2 つの別個の構文から選択できます。

  • プロパティに設定する値を明示的に定義する
  • アロー関数を使用してプロパティに設定する値を返す

これら両方のアプローチを見てみましょう。姓名を 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 プロパティの設定フックを定義しました。また、アロー関数を使用してプロパティに設定する値を返す、lastName プロパティの設定フックも定義しました。

タイプの互換性

プロパティに型宣言がある場合、そのセットフックにも互換性のある型セットが必要です。次の例では、firstName の set フックには型宣言がありませんが、プロパティ自体には 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
ログイン後にコピー

In the code above, we've defined a fullName property that has both a get and set hook. The get hook returns the full name by concatenating the first and last name together. The set hook splits the full name into the first and last name and sets them on their respective properties.

You may have also noticed that we're not setting a value on the fullName property itself. Instead, if we need to read the value of the fullName property, the get hook will be called to build up the full name from the first and last name properties. I've done this to highlight that you can have a property that doesn't have a value set directly on it, but instead, the value is calculated from other properties.

Using Property Hooks on Promoted Properties

A cool feature of property hooks is that you can also use them with constructor promoted properties.

Let's check out an example of a class that isn't using promoted properties and then look at what it might look like using promoted properties.

Our User class might look like so:

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;
    }
}
ログイン後にコピー

We could promote the firstName and lastName properties into the constructor and define their set logic directly on the property:

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);
        }
    ) {
        //
    }
}  
ログイン後にコピー

Write-only Hooked Properties

If you define a hooked property with a setter that doesn't actually set a value on the property, then the property will be write-only. This means you can't read the value of the property, you can only set it.

Let's take our User class from the previous example and modify the fullName property to be write-only by removing the get hook:

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!
ログイン後にコピー

If we were to run the code above, we'd see the following error being thrown when attempting to access the fullName property:

Fatal error: Uncaught Error: Property User::$fullName is write-only
ログイン後にコピー

Read-only Hooked Properties

Similarly, a property can be read-only.

For example, imagine we only ever want the fullName property to be generated from the firstName and lastName properties. We don't want to allow the fullName property to be set directly. We can achieve this by removing the set hook from the fullName property:

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!
    }
}
ログイン後にコピー

If we were to try and run the code above, the following error would be thrown because we're trying to set the fullName property directly:

Uncaught Error: Property User::$fullName is read-only
ログイン後にコピー

Using the "readonly" keyword

You can still make our PHP classes readonly even if they have hooked properties. For example, we may want to make the User class readonly:

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;
    }
}
ログイン後にコピー

However, a hooked property cannot use the readonly keyword directly. For example, this class would be invalid:

class User
{
    public readonly string $fullName {
        get => $this->firstName.' '.$this->lastName;
    }

    public function __construct(
        string $firstName,
        string $lastName,
    ) {
        $this->firstName = $firstName;
        $this->lastName = $lastName;
    }
}
ログイン後にコピー

The above code would throw the following error:

Fatal error: Hooked properties cannot be readonly
ログイン後にコピー

The "PROPERTY" Magic Constant

In PHP 8.4, a new magic constant called __PROPERTY__ has been introduced. This constant can be used to reference the property name within the property hook.

Let's take a look at an example:

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;
    }
}
ログイン後にコピー

In the code above, we can see that using __PROPERTY__ inside the lastName property's setter will output the property name lastName. However, it's also worth noting that trying to use this constant in an attempt to set the property value will trigger an error:

Fatal error: Uncaught Error: Must not write to virtual property User::$lastName
ログイン後にコピー

There's a handy use case example for the __PROPERTY__ magic constant that you can check out on GitHub: https://github.com/Crell/php-rfcs/blob/master/property-hooks/examples.md.

Hooked Properties in Interfaces

PHP 8.4 also allows you to define publicly accessible hooked properties in interfaces. This can be useful if you want to enforce that a class implements certain properties with hooks.

Let's take a look at an example interface with hooked properties declared:

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; }
}
ログイン後にコピー

In the interface above, we're defining that any classes implementing the Nameable interface must have:

  • A fullName property that is at least publicly gettable. This can be achieved by defining a get hook or not defining a hook at all.
  • A firstName property that is at least publicly gettable.
  • A lastName property that is at least publicly settable. This can be achieved by defining a property which has a set hook or not defining a hook at all. But if the class is read-only then the property must have a set hook.

This class that implements the Nameable interface would be valid:

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;
    }
}
ログイン後にコピー

The class above would be valid because the fullName property has a get hook to match the interface definition. The firstName property only has a set hook, but is still publicly accessible so it satisfies the criteria. The lastName property doesn't have a get hook, but it is publicly settable so it satisfies the criteria.

Let's update our User class to enforce a get and set hook for the fullName property:

interface Nameable
{
    public string $fullName { get; set; }

    public string $firstName { get; }

    public string $lastName { set; }
}
ログイン後にコピー

Our User class would no longer satisfy the criteria for the fullName property because it doesn't have a set hook defined. It would cause the following error to be thrown:

Fatal error: Class User contains 1 abstract methods and must therefore be declared abstract or implement the remaining methods (Nameable::$fullName::set)
ログイン後にコピー

Hooked Properties in Abstract Classes

Similar to interfaces, you can also define hooked properties in abstract classes. This can be useful if you want to provide a base class that defines hooked properties that child classes must implement. You can also define the hooks in the abstract class and have them be overridden in the child classes.

For example, let's make a Model abstract class that defines a name property that must be implemented by child classes:

abstract class Model
{
    abstract public string $fullName {
        get => $this->firstName.' '.$this->lastName;
        set;
    }

    abstract public string $firstName { get; }

    abstract public string $lastName { set; }
}
ログイン後にコピー

In the abstract class above, we're defining that any classes that extend the Model class must have:

  • A fullName property that is at least publicly gettable and settable. This can be achieved by defining a get and set hook or not defining a hook at all. We've also defined the get hook for the fullName property in the abstract class so we don't need to define it in the child classes, but it can be overridden if needed.
  • A firstName property that is at least publicly gettable. This can be achieved by defining a get hook or not defining a hook at all.
  • A lastName property that is at least publicly settable. This can be achieved by defining a property which has a set hook or not defining a hook at all. But if the class is read-only then the property must have a set hook.

We could then create a User class that extends the Model class:

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;
    }
}
ログイン後にコピー

Conclusion

Hopefully, this article should have given you an insight into how PHP 8.4 property hooks work and how you might be able to use them in your PHP projects.

I wouldn't worry too much if this feature seems a little confusing at first. When I first saw it, I was a little confused too (especially with how they work with interfaces and abstract classes). But once you start tinkering with them, you'll soon get the hang of it.

I'm excited to see how this feature will be used in the wild and I'm looking forward to using it in my projects when PHP 8.4 is released.

If you enjoyed reading this post, you might be interested in checking out my 220+ page ebook "Battle Ready Laravel" which covers similar topics in more depth.

Or, you might want to check out my other 440+ page ebook "Consuming APIs in Laravel" which teaches you how to use Laravel to consume APIs from other services.

If you're interested in getting updated each time I publish a new post, feel free to sign up for my newsletter.

Keep on building awesome stuff! ?

以上がPHP プロパティフックの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート