"일꾼이 일을 잘하려면 먼저 도구를 갈고 닦아야 한다." - 공자, 『논어』.
첫 장 > 프로그램 작성 > PHP 속성 후크

PHP 속성 후크

2024-08-26에 게시됨
검색:279

PHP  Property Hooks

Introduction

PHP 8.4 will be released in November 2024 and will be bringing a cool new feature: property hooks.

In this article, we're going to take a look at what property hooks are and how you might use them in your PHP 8.4 projects.

As a side note, you might also be interested in checking out my other article which shows you the new array functions which are being added in PHP 8.4.

What are PHP Property Hooks?

Property hooks allow you to define custom getter and setter logic for class properties without having to write separate getter and setter methods. This means you can define the logic directly in the property declaration so you can directly access a property (like $user->firstName) without having to remember to call a method (like $user->getFirstName() and $user->setFirstName()).

You can check out the RFC for this feature at https://wiki.php.net/rfc/property-hooks

If you're a Laravel developer, as you're reading this article, you might notice that the hooks look very similar to accessors and mutators in Laravel models.

I quite like the look of the property hooks feature and I imagine it's something I'll be using in my projects when PHP 8.4 is released.

To understand how property hooks work, let's take a look at some example usage.

The "get" Hook

You can define a get hook that will be called whenever you try to access a property.

For example, imagine you have a simple User class that accepts a firstName and lastName in the constructor. You might want to define a fullName property that concatenates the first and last names together. To do this, you could define a get hook for the fullName property:

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

In the example above, we can see that we've defined a get hook for the fullName property that returns a value which is calculated by concatenating the firstName and lastName properties together. We can clean this up a little bit more by using a syntax similar to arrow functions too:

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

Type Compatibility

It's important to note that the returned value from the getter has to be compatible with the type of the property.

If strict types aren't enabled, the value will be type-juggled to the property type. For example, if you return an integer from a property that is declared as a string, the integer will be converted to a string:

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"

In the above example, even though we've specified 123 as an integer to be returned, "123" is returned as a string because the property is a string.

We can add declare(strict_types=1); to the top of the code like so to enable strict type-checking:

declare(strict_types=1);

class User
{
    public string $fullName {
        get {
            return 123;
        }
    }

    public function __construct(
        public readonly string $firstName,
        public readonly string $lastName,
    ) {
        //
    }
}

Now this would cause an error to be thrown because the return value is an integer, but the property is a string:

Fatal error: Uncaught TypeError: User::$fullName::get(): Return value must be of type string, int returned

The "set" Hook

PHP 8.4 property hooks also allow you to define a set hook. This is called whenever you try to set a property.

You can choose between two separate syntaxes for the set hook:

  • Explicitly defining the value to set on the property
  • Using an arrow function to return the value to set on the property

Let's look at both of these approaches. We'll imagine we want to uppercase the first letters of the first and last name when they're set on the User class:

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

As we can see in the example above, we've defined a set hook for the firstName property that uppercases the first letter of the name before setting it on the property. We've also defined a set hook for the lastName property that uses an arrow function to return the value to set on the property.

Type Compatibility

If the property has a type declaration, then its set hook must have a compatible type set too. The following example will return an error because the set hook for firstName doesn't have a type declaration, but the property itself has a type declaration of 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;
    }
}

Attempting to run the above code would result in the following error being thrown:

Fatal error: Type of parameter $name of hook User::$firstName::set must be compatible with property type

Using "get" and "set" Hooks Together

You aren't limited to using the get and set hooks separately. You can use them together in the same property.

Let's take a simple example. We'll imagine we have a fullName property on our User class. When we set the property, we'll split the full name into the first and last name. I know this is a naive approach and there are much better solutions, but it's purely for the sake of example to highlight the hooked properties.

The code may look something like so:

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

릴리스 선언문 이 글은 https://dev.to/ashallendesign/php-84-property-hooks-4nbd?1에서 복제됩니다.1 침해 내용이 있는 경우, [email protected]으로 연락하여 삭제하시기 바랍니다.
최신 튜토리얼 더>
  • @Transactional에서 기본 격리 및 전파 매개변수를 조정해야 하는 시기와 이유는 무엇입니까?
    @Transactional에서 기본 격리 및 전파 매개변수를 조정해야 하는 시기와 이유는 무엇입니까?
    @Transactional의 격리 및 전파 매개변수Spring의 @Transactional 주석에서 두 가지 중요한 매개변수는 데이터베이스 트랜잭션의 동작, 즉 격리와 전파를 정의합니다. . 이 문서에서는 기본값 조정을 고려해야 하는 시기와 이유를 살펴봅니다.전파전파는...
    프로그램 작성 2024-11-06에 게시됨
  • OpenAPI 트리머 Python 도구
    OpenAPI 트리머 Python 도구
    OpenAPI 트리머로 OpenAPI 파일 단순화 대규모 OpenAPI 파일을 관리하는 것은 번거로울 수 있으며, 특히 특정 작업에 API의 작은 부분만 필요한 경우에는 더욱 그렇습니다. 이것이 OpenAPI 트리머가 유용한 곳입니다. 관심 있는 엔드포...
    프로그램 작성 2024-11-06에 게시됨
  • PHP: 동적 웹사이트의 비밀 소스 공개
    PHP: 동적 웹사이트의 비밀 소스 공개
    PHP(Hypertext Preprocessor)는 동적 및 대화형 웹사이트를 만드는 데 널리 사용되는 서버측 프로그래밍 언어입니다. 간단한 구문, 동적 콘텐츠 생성 기능, 서버 측 처리 및 신속한 개발 기능으로 잘 알려져 있으며 대부분의 웹 호스트에서 지원됩니다. P...
    프로그램 작성 2024-11-06에 게시됨
  • 깔끔하고 유지 관리 가능한 코드를 위한 JavaScript의 변수 명명 모범 사례
    깔끔하고 유지 관리 가능한 코드를 위한 JavaScript의 변수 명명 모범 사례
    소개: 코드 명확성 및 유지 관리 강화 깨끗하고 이해하기 쉽고 유지 관리가 가능한 코드를 작성하는 것은 모든 JavaScript 개발자에게 중요합니다. 이를 달성하는 주요 측면은 효과적인 변수 이름 지정을 통해서입니다. 이름이 잘 지정된 변수를 사용하면 코드를 더 쉽게...
    프로그램 작성 2024-11-06에 게시됨
  • Spring AOP의 내부 작동 방식 공개
    Spring AOP의 내부 작동 방식 공개
    이 포스트에서는 Spring의 AOP(Aspect-Oriented 프로그래밍)의 내부 메커니즘을 쉽게 설명할 것입니다. AOP가 종종 "마법"의 형태로 간주되는 로깅과 같은 기능을 달성하는 방법을 이해하는 데 중점을 둘 것입니다. 핵심 Java 구현을...
    프로그램 작성 2024-11-06에 게시됨
  • JavaScript E 릴리스 노트: 최신 JavaScript의 성능 활용
    JavaScript E 릴리스 노트: 최신 JavaScript의 성능 활용
    공식적으로 ECMAScript 2015로 알려진 JavaScript ES6에는 개발자가 JavaScript를 작성하는 방식을 변화시키는 중요한 개선 사항과 새로운 기능이 도입되었습니다. ES6를 정의하고 JavaScript 프로그래밍을 더욱 효율적이고 즐겁게 만들어주는...
    프로그램 작성 2024-11-06에 게시됨
  • Javascript의 POST 요청 이해
    Javascript의 POST 요청 이해
    function newPlayer(newForm) { fetch("http://localhost:3000/Players", { method: "POST", headers: { 'Content-Type': 'application...
    프로그램 작성 2024-11-06에 게시됨
  • Savitzky-Golay 필터링을 사용하여 잡음이 있는 곡선을 완화하는 방법은 무엇입니까?
    Savitzky-Golay 필터링을 사용하여 잡음이 있는 곡선을 완화하는 방법은 무엇입니까?
    잡음이 있는 데이터에 대한 곡선 평활화: Savitzky-Golay 필터링 탐색데이터세트를 분석할 때 잡음이 있는 곡선을 평활화하는 문제는 다음과 같습니다. 명확성을 높이고 기본 패턴을 찾아냅니다. 이 작업에 특히 효과적인 방법 중 하나는 Savitzky-Golay 필...
    프로그램 작성 2024-11-06에 게시됨
  • varargs 메서드 오버로드
    varargs 메서드 오버로드
    varargs 메서드 오버로드 가변 길이 인수를 취하는 메서드를 오버로드할 수 있습니다. 이 프로그램은 varargs 메서드를 오버로드하는 두 가지 방법을 보여줍니다. 1 다양한 varargs 매개변수 유형: vaTest(int...) 및 vaTest(boolean.....
    프로그램 작성 2024-11-06에 게시됨
  • 클래식 클래스 구성 요소 내에서 React Hooks를 활용하는 방법은 무엇입니까?
    클래식 클래스 구성 요소 내에서 React Hooks를 활용하는 방법은 무엇입니까?
    React Hooks를 클래식 클래스 구성요소와 통합React 후크는 클래스 기반 구성요소 설계에 대한 대안을 제공하지만 기존 클래스에 통합하여 점진적으로 채택하는 것도 가능합니다. 구성 요소. 이는 고차 컴포넌트(HOC)를 사용하여 달성할 수 있습니다.다음 클래스 컴...
    프로그램 작성 2024-11-06에 게시됨
  • Vite 및 React를 사용하여 더 빠른 단일 페이지 애플리케이션(SPA)을 구축하는 방법
    Vite 및 React를 사용하여 더 빠른 단일 페이지 애플리케이션(SPA)을 구축하는 방법
    현대 웹 개발 세계에서 SPA(단일 페이지 응용 프로그램)는 동적이고 빠르게 로드되는 웹 사이트를 만드는 데 널리 사용됩니다. 사용자 인터페이스 구축을 위해 가장 널리 사용되는 JavaScript 라이브러리 중 하나인 React를 사용하면 SPA 개발이 간단해집니다. ...
    프로그램 작성 2024-11-06에 게시됨
  • JavaScript의 문자열 연결에 대한 단계별 가이드
    JavaScript의 문자열 연결에 대한 단계별 가이드
    JavaScript의 문자열 연결은 두 개 이상의 문자열을 결합하여 단일 문자열을 형성하는 프로세스입니다. 이 가이드에서는 연산자, = 연산자, concat() 메서드 및 템플릿 리터럴 사용을 포함하여 이를 달성하기 위한 다양한 방법을 살펴봅니다. 각 방법은 간단하...
    프로그램 작성 2024-11-06에 게시됨
  • Web UX: 사용자에게 의미 있는 오류 표시
    Web UX: 사용자에게 의미 있는 오류 표시
    사용자 중심적이고 사용자 친화적인 웹사이트를 갖는 것은 때로는 까다로울 수 있습니다. 전체 개발팀이 기능과 핵심 비즈니스에 가치를 더하지 않는 일에 더 많은 시간을 할애하게 되기 때문입니다. 그러나 단기적으로는 사용자에게 도움이 되고 장기적으로는 가치를 더할 수 있습니...
    프로그램 작성 2024-11-06에 게시됨
  • 소규모 클래스 조작자
    소규모 클래스 조작자
    소규모 클래스 매니퓰레이터의 새로운 주요 릴리스 코드가 완전히 리팩터링되었으며 새로운 속성 조작 지원이 코딩되었습니다. 다음은 조작의 예입니다. $classFile = \Small\ClassManipulator\ClassManipulator::fromProjec...
    프로그램 작성 2024-11-06에 게시됨
  • 머신러닝 프로젝트의 효과적인 모델 버전 관리
    머신러닝 프로젝트의 효과적인 모델 버전 관리
    머신러닝(ML) 프로젝트에서 가장 중요한 구성 요소 중 하나는 버전 관리입니다. 기존 소프트웨어 개발과 달리 ML 프로젝트 관리에는 소스 코드뿐만 아니라 시간이 지남에 따라 발전하는 데이터 및 모델도 포함됩니다. 이를 위해서는 실험을 관리하고, 최상의 모델을 선택하고,...
    프로그램 작성 2024-11-06에 게시됨

부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.

Copyright© 2022 湘ICP备2022001581号-3