php[architect] logo

Want to check out an issue? Sign up to receive a special offer.

Property Hooks Are Coming To PHP 8.4!

Posted by on October 14, 2024

One of the most widely discussed features coming to PHP 8.4 is Property Hooks. Property hooks allow us to add behavior that is specific to a single property while allowing that behavior to not interfere with existing aspects of PHP.
This is going to be a game changer for us as PHP developers. In this article, we’ll discuss how to use PHP’s property hooks.

What Are PHP Property Hooks?

At a high level, property hooks allow you to add custom logic directly to class properties. This means you can interact with properties like $user->fullName as if it was a getter or
setter function without needing separate methods like $user->getFullName() or $user->setFullName().

We define property hooks by defining a set and get operation that will be performed while setting or getting a property.

I think this section from the RFC for Property Hooks helps illustrate the use case for property hooks.

A primary use case for hooks is actually to not use them, but retain the ability to do so in the future, should it become necessary. In particular, developers often implement getFoo/setFoo methods on a property not because they are necessary, but because they might become necessary in a hypothetical future, and changing from a property to a method at that point becomes an API change.

Creating a “get” Hook

The get hook is used to define what happens when a property is read.

For example, if we have a User class where we’re tracking the user’s first and last names but we have a need to quickly get their “full name”. We can define the first and last properties using constructor property promotion and then define a fullName property that will have a get property hook. Then we can easily output the fullName.

Note: I took this from the RFC and modified it a little as it’s a great example!

<?php
class User
{
    public function __construct(public string $first, public string $last) {}

    public string $fullName {
        // Override the "read" action with arbitrary logic.
        get => "{$this->first} {$this->last}";
    }
}

$user = new User("Scott", "Keck-Warren");
// output: 
// Scott Keck-Warren
echo $user->fullName, PHP_EOL;

Creating a “set” Hook

The set hook is used to define what happens when we assign a property.

If we go back to our User class we can add aset hook to our fullName property that willexplode the value we pass to it.

<?php
class User
{
    public function __construct(public string $first, public string $last) {}

    public string $fullName {
        // Override the "write" action with arbitrary logic.
        set { 
            [$this->first, $this->last] = explode(" ", $value, 2);
        }
    }
}


$user = new User("Scott", "Keck-Warren");
$user->fullName = "Joe Keck-Warren";
// output:
// Joe
// Keck-Warren
echo $user->first, PHP_EOL;
echo $user->last, PHP_EOL;

Readonly andWriteonly Properties

One of the interesting features of property hooks is that we don’t need to define both the get and set operations so we can create some interesting behaviors.

If we only define the get property hook, we will create a read-only property.

<?php
class User
{
    public function __construct(public string $first, public string $last) {}
    
    public string $fullName {
        // Override the "read" action with arbitrary logic.
        get => "{$this->first} {$this->last}";
    }
}

$user = new User("Scott", "Keck-Warren");
// Uncaught Error: Property User::$fullName is read-only in /app/test.php:13
$user->fullName = "Junk Value";

If we only define the set property hook we will create a write-only property.

<?php
class User
{
    public function __construct(public string $first, public string $last) {}
    
    public string $fullName {
        // Override the "write" action with arbitrary logic.
        set { 
            [$this->first, $this->last] = explode(" ", $value, 2);
        }
    }
}


$user = new User("Scott", "Keck-Warren");
// output: 
// Property User::$fullName is write-only
echo $user->fullName, PHP_EOL;

The amazing application of this is we can use it to create a property that encrypts or hashes a value while keeping the hashed value secure from the outside by making it private.

<?php
class User
{
    public string $password {
        // Override the "write" action with arbitrary logic.
        set { 
            $this->hashedPassword = password_hash($value, PASSWORD_DEFAULT);
        }
    }

    private string $hashedPassword;
}


$user = new User();
$user->password = "CorrectHorseBatteryStaple";
// Uncaught Error: Cannot access private property
echo $user->hashedPassword, PHP_EOL;
// Uncaught Error: Property User::$password is write-only
echo $user->password, PHP_EOL;

One of the discussions about this online is that it makes it easy for us to create “confusing” code because while we expect the property to just set the value it’s doing something behind the scenes. This is no
different than having a setPassword() that does the same thing.

Asymmetric Visibility

Another feature added to PHP 8.4 as part of the property hooks functionality is the ability to define properties that have Asymmetric Visibility (https://wiki.php.net/rfc/asymmetric-visibility-v2). This means that we can define a property that can be publicly “get” but can only be “set” by the class (private) or its children (protected).

We do this by declaring the property as public like normal but then adding either private(set) or protected(set) before they type like the code below. We’ll now get an error if we attempt to access it.

<?php
class User
{
    public protected(set) string $hashedPassword;
}

$user = new User();
// Uncaught Error: Cannot modify protected(set) property
$user->hashedPassword = password_hash("CorrectHorseBatteryStaple", PASSWORD_DEFAULT);

Now we can define a child class that can modify the property.

class StandardUser extends User 
{
    public function setHashedPassword(string $value): void 
    {
        $this->hashedPassword = password_hash($value, PASSWORD_DEFAULT);
    }
}

$user = new StandardUser();
$user->setHashedPassword("CorrectHorseBatteryStaple");

Ideally, the child class understands how to “correctly” handle this so it’s not going to mess up what the parent is doing. It’s really an awesome addition;

Interfaces

Another piece of functionality we’re getting with property hooks is the ability to define property hooks inside of our interfaces so our classes are forced to implement them.

The piece of this that’s really powerful is we can define property inside our interface as readonly by including{ get; } after it or read and write using{ get; set;} then we can use constructor property promotion to define the property.

<?php
// again taken from the RFC
interface Named
{
    public string $fullName { get; }
}

class User implements Named
{
    public function __construct(public readonly string $fullName) {}
}

$user = new User("Scott Keck-Warren");

What You Need To Know

  1. Property hooks are a feature being added to PHP 8.4
  2. Property hooks allow us to “override” the default get and set
    operations on properties
  3. Define properties with asymmetric visibility
  4. It can also define properties in interfaces

Tags: , ,
 

Leave a comment

Use the form below to leave a comment: