Php property hooks (now i understand)
Tags: php
Published: 2024-06-07 12:58:05

Update: this post is heavy modified, because i didn't understand the goal of the property hooks. If you want to see the constructor property promotion validation examples that were in this post, i made a [new post](https://dev.to/xwero/how-to-validate-constructor-arguments-when-using-constructor-property-promotion-5dp6) to let them shine on their own merits.

Yesterday morning I reacted on a post about the possible new php 8.4 feature, property hooks.
{% embed https://dev.to/spo0q/php-84-property-hooks-45i8 %}
After a comment from the author, I saw my mistake and now this post is my take on what the property hooks feature is about.

## My confusion

Based on examples like these

```php
class User 
{
    public string $name {
        set {
            if (strlen($value) === 0) {
                throw new ValueError("Name must be non-empty");
            }
            $this->name = $value;
        }
    }
 
    public function __construct(string $name) {
        $this->name = $name;
    }
}
```

```php
interface Named
{
    // Objects implementing this interface must have a readable
    // $fullName property.  That could be satisfied with a traditional
    // property or a property with a "get" hook.
    public string $fullName { get; }
}
 
// The "User" class above satisfies this interface, but so does:
 
class SimpleUser implements Named
{
    public function __construct(public readonly string $fullName) {}
}
```
I thought the feature was a step back from constructor property promotion. 
The second example should have given me a clue, but I was too blind to see it.

## What are property hooks then?

When you create a class in php you can add or get properties after the instantiation by calling them directly. 

```php
$a = new A();
$a->property = 'a';
```

When calling a property directly php has a mechanism to make this work if the property is not explicitly set in the class. In the RFC they mention the term virtual property. The php term is dynamic property.

If you want to add a dynamic property you can do two things; create a setter method or use the __set magic method.

Property hooks provide a third method to do this with the set hook.
And the same goes for getting a dynamic property; use a getter method,  use the __get magic method or use the set hook.

## Should you use property hooks?

Since php 8.2 there is a [deprecation notice](https://www.php.net/manual/en/migration82.deprecated.php#migration82.deprecated.core.dynamic-properties) when calling dynamic properties. There is an attribute to allow dynamic properties so if you want to use it you can.

If you are using the __set and __get methods, yes as soon as possible.

If you have a php 8.x code base there are going to be fewer cases where the feature makes sense. 
One case where I can see a use is for data-object classes, most of the times the properties are public.

```php
interface Named
{
    public string $fullName { get; }
}

#[\AllowDynamicProperties]
class Person implements Named {
  public string $firstName = {
    set {
      if (strlen($value) === 0) {
         throw new ValueError("First name must be non-empty");
      }
      $this->firstName = $value;
    }
  } // dynamic property
  public string $lastName = ''; // actual property

  public string $fullName {
    get => "$this->firstName $this->lastName";
  }
}

$p = new Person();
$p->firstName = 'Me';
echo $p->fullName;
```
It allows you to interact with the data-object class only using properties. 

It is getting murkier if the properties are constructor arguments, because then you need the validation in two places.

```php
#[\AllowDynamicProperties]
class Person {
  public string $firstName = {
    set {
      $this->validateFirstName($value);
    }
  }

  public function __construct(string $firstName) {
    $this->validateFirstName($firstName);
  }

  private function validateFirstName(string $n) {
     if (strlen($n) === 0) {
         throw new ValueError("First name must be non-empty");
      }
      $this->firstName = $n;
  }

$p = new Person('Me'); // not validated by the property hook
$p->firstName = 'You'; // validated by the property hook
```
This is where I lost the distinction between dynamic and actual property. 
Because they looks so alike.

In the RFC they only mention there is an error when a trait and a class have the same property. No mention was is going to happen when the same property is in two interfaces that are implemented in a class. I guess it also creates an error?

## Conclusion

With the php 8.2 deprecation warning in mind, when will they decide to make it an error? 
I guess they won't remove dynamic property feature all together because then the property hooks are going to stop working.

It looks like it is a [spaceship operator](https://www.php.net/manual/en/migration70.new-features.php#migration70.new-features.spaceship-op) niche feature that gets a lot of publicity because it of its novelty.

It will not be wide spread pattern in code bases.