Get "PHP 8 in a Nuthshell" (Now with PHP 8.4)
Amit Merchant

Amit Merchant

A blog on PHP, JavaScript, and more

Property hooks are coming in PHP 8.4

Accessing or setting the value of a class property is comman task in object oriented programming. There are a few ways to do this in PHP. Let’s discuss them first.

The precursor

Take the following class for example.

class User
{
    private string $email;
}

As you can tell, we have a private property $email in the class. Now, we can define getter and setter methods to read and write the value of the property respectively like so.

class User
{
    private string $email;

    public function getEmail(): string
    {
        return $this->email;
    }

    public function setEmail(string $email): void
    {
        $this->email = $email;
    }
}

$user = new User();
$user->setEmail('[email protected]');
echo $user->getEmail(); // [email protected]

This is a pretty traditional approach and people have been using it for a long time.

Alternatively, in PHP 8.3, we can shorten this further using the constructor property promotion like so.

class User
{
    public function __construct(public string $email) {}
}

$user = new User('[email protected]');
echo $user->email; // [email protected]

This is a pretty neat approach and it’s a bit more concise the using the getter and setter methods.

We can use __get and __set magic methods to achieve the same result as well. But that’s very verbose, error-prone, and not friendly for static analysis tools like PHPStan.

PHP 8.4 is going to make this key aspect better by introducing property hooks.

Property hooks

According to this RFC, property hooks are a new feature that might land in PHP 8.4 that allows you to define custom logic for property access and mutation. This can be useful for a variety of use cases, such as mutation, logging, validation, or caching.

Essentially, property hooks allow you to define additional behavior on class properties mainly using two hooks: get and set. And this will be individual for certain properties.

The set hook

Here’s how we can write a set hook for the $email property in the previous example.

class User
{
    public string $email {
        set (string $value) {
            // validate the email address
            if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
                throw new InvalidArgumentException(
                    'Invalid email address'
                );
            }
            
            // Set the value
            $this->email = $value;
        }
    }
}

As you can tell, hooks are enclosed in curly braces that come right after the property name. We can then define the hooks inside this code block.

The set hook body is an arbitrarily complex method body, which accepts one argument. If specified, it must include both the type and parameter name. Here, we can validate or modify the value of the property before it is set.

So, when a value is set to the $email property, the set hook will be called and the value will be validated before it is set.

$user = new User();
$user->email = 'example.com'; // This will throw an exception

$user = new User();
$user->email = '[email protected]';
echo $user->email; // [email protected]

There’s also a shorthand syntax for defining the set hook using the => operator.

class User
{
    public string $email {
        set => strtolower($value);
    }
}

Here the $value is assumed to be the value of the property and the strtolower function will be called on it.

The get hook

The get hook on the other hand allows you to define custom logic for property access. This can be useful for properties that need to be changed before they are returned to the user.

For instance, if the User class has two properties, $firstName and $lastName, we can define a get hook for the $fullName property like so.

class User
{
    public function __construct(
        public string $firstName, public string $lastName
    ) {}

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

$user = new User('John', 'Doe');
echo $user->fullName; // John Doe    

As you can tell, the get hook does not accept any arguments. It simply returns the value of the property.

So, when a value is accessed from the $fullName property, the get hook will be called and the value will be returned based on the logic defined in the hook.

There’s a shorthand syntax for defining the get hook using the => operator.

class User
{
    public function __construct(
        public string $firstName, 
        public string $lastName
    ) {}

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

This is equivalent to the previous example.

Using hooks with interfaces

The hooks can be specified on interfaces as well.

interface Base
{
    // 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; }
}
 
class SimpleUser implements Base
{
    // The "get" hook is optional, and if not specified, the
    // property will be readable without a "get" hook.
    public function __construct(public string $fullName) {}
}

as you can see, the $fullName property is readable without a get hook. But if we define a get hook for the property, it will be readable with a get hook.

Things to note

There are things to consider while using property hooks.

  • Hooks are only available on object properties. So, static properties cannot have hooks.
  • Property hooks override any read or write behavior of the property.
  • Property hooks have access to all public, private, or protected methods of the object, as well as any public, private, or protected properties, including properties that may have their own property hooks.
  • Setting references on hooked properties is not allowed since any attempted modification of the value by reference would bypass a set hook, if one is defined.
  • A child class may define or redefine individual hooks on a property by redefining the property and just the hooks it wishes to override. The type and visibility of the property are subject to their own rules independently. So, each hook overrides parent implementations independently of each other.

In Closing

Property hooks are a powerful feature that allows you to customize the behavior of properties in a way that is a lot more clearer, concise, and flexible than other approaches. They are especially useful when you want to add custom logic to properties that are read or written by the object.

In the RFC, it’s mentioned that although there are two property hooks currently, there’s a possibility of adding more in the future which will make property hooks even more powerful.

Although the RFC is still in the voting phase, it looks like it will be included in PHP 8.4 since most of the votes are in favor of it so far.

Read more about property hooks in the RFC here.

Learn the fundamentals of PHP 8 (including 8.1, 8.2, and 8.3), the latest version of PHP, and how to use it today with my new book PHP 8 in a Nutshell. It's a no-fluff and easy-to-digest guide to the latest features and nitty-gritty details of PHP 8. So, if you're looking for a quick and easy way to PHP 8, this is the book for you.

Like this article?

Buy me a coffee

👋 Hi there! I'm Amit. I write articles about all things web development. You can become a sponsor on my blog to help me continue my writing journey and get your brand in front of thousands of eyes.

Comments?