PHP Property Hooks

PHPz
Release: 2024-08-21 20:44:50
Original
665 people have browsed it

PHP  Property Hooks

소개

PHP 8.4는 2024년 11월에 출시될 예정이며 속성 후크라는 멋진 새 기능을 선보일 예정입니다.

이 기사에서는 속성 후크가 무엇인지, PHP 8.4 프로젝트에서 이를 어떻게 사용할 수 있는지 살펴보겠습니다.

참고로, PHP 8.4에 추가되는 새로운 배열 함수를 보여주는 제 다른 기사도 확인해 보세요.

PHP 속성 후크란 무엇입니까?

속성 후크를 사용하면 별도의 getter 및 setter 메서드를 작성하지 않고도 클래스 속성에 대한 사용자 정의 getter 및 setter 논리를 정의할 수 있습니다. 즉, 속성 선언에서 논리를 직접 정의할 수 있으므로 메소드($user->getFirstName() 및 $user-) 호출을 기억할 필요 없이 속성($user->firstName 등)에 직접 액세스할 수 있습니다. >setFirstName()).

https://wiki.php.net/rfc/property-hooks

에서 이 기능에 대한 RFC를 확인할 수 있습니다.

Laravel 개발자라면 이 글을 읽으면서 후크가 Laravel 모델의 접근자 및 돌연변이자와 매우 유사하다는 것을 알 수 있을 것입니다.

저는 속성 후크 기능의 모양이 매우 마음에 들고 PHP 8.4가 출시되면 내 프로젝트에서 사용할 기능이 될 것이라고 생각합니다.

속성 후크의 작동 방식을 이해하기 위해 몇 가지 사용 예를 살펴보겠습니다.

"가져오기" 후크

속성에 액세스하려고 할 때마다 호출되는 get 후크를 정의할 수 있습니다.

예를 들어 생성자에서 firstName과 lastName을 허용하는 간단한 User 클래스가 있다고 가정해 보겠습니다. 이름과 성을 함께 연결하는 fullName 속성을 정의할 수 있습니다. 이렇게 하려면 fullName 속성에 대한 get 후크를 정의하면 됩니다.

으아악

위의 예에서는 firstName과 lastName 속성을 함께 연결하여 계산된 값을 반환하는 fullName 속성에 대한 get 후크를 정의했음을 볼 수 있습니다. 화살표 함수와 유사한 구문을 사용하여 이를 좀 더 정리할 수 있습니다.

으아악

유형 호환성

getter에서 반환된 값은 속성 유형과 호환되어야 한다는 점에 유의하는 것이 중요합니다.

엄격한 유형이 활성화되지 않은 경우 값은 속성 유형에 따라 유형 조정됩니다. 예를 들어 문자열로 선언된 속성에서 정수를 반환하는 경우 정수는 문자열로 변환됩니다.

으아악

위 예에서는 반환할 정수로 123을 지정했지만 속성이 문자열이므로 "123"이 문자열로 반환됩니다.

declar(strict_types=1);을 추가할 수 있습니다. 엄격한 유형 검사를 활성화하려면 다음과 같이 코드 상단에 추가하세요.

으아악

이제 반환 값은 정수이지만 속성은 문자열이므로 오류가 발생합니다.

으아악

"세트" 후크

PHP 8.4 속성 후크를 사용하면 세트 후크를 정의할 수도 있습니다. 이는 속성을 설정하려고 할 때마다 호출됩니다.

세트 후크에 대해 두 가지 별도 구문 중에서 선택할 수 있습니다.

  • 속성에 설정할 값을 명시적으로 정의
  • 화살표 함수를 사용하여 속성에 설정할 값을 반환

이 두 가지 접근 방식을 모두 살펴보겠습니다. User 클래스에 설정할 때 이름과 성의 첫 글자를 대문자로 바꾸고 싶다고 가정하겠습니다.

으아악

위의 예에서 볼 수 있듯이 속성에 이름을 설정하기 전에 이름의 첫 글자를 대문자로 바꾸는 firstName 속성에 대한 설정 후크를 정의했습니다. 또한 화살표 함수를 사용하여 속성에 설정할 값을 반환하는 lastName 속성에 대한 설정 후크를 정의했습니다.

유형 호환성

속성에 유형 선언이 있는 경우 해당 설정 후크에도 호환 가능한 유형이 설정되어 있어야 합니다. 다음 예에서는 firstName에 대한 설정 후크에 유형 선언이 없지만 속성 자체에는 string 유형 선언이 있기 때문에 오류를 반환합니다.

으아악

위 코드를 실행하려고 하면 다음 오류가 발생합니다.

으아악

"get"과 "set" Hook을 함께 사용하기

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
Copy after login

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;
    }
}
Copy after login

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);
        }
    ) {
        //
    }
}  
Copy after login

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!
Copy after login

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
Copy after login

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!
    }
}
Copy after login

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
Copy after login

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;
    }
}
Copy after login

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;
    }
}
Copy after login

The above code would throw the following error:

Fatal error: Hooked properties cannot be readonly
Copy after login

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;
    }
}
Copy after login

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
Copy after login

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; }
}
Copy after login

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;
    }
}
Copy after login

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; }
}
Copy after login

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)
Copy after login

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; }
}
Copy after login

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;
    }
}
Copy after login

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! ?

The above is the detailed content of PHP Property Hooks. For more information, please follow other related articles on the PHP Chinese website!

source:dev.to
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!