# PHP 8.4 is coming: property hooks and asymmetric visibility

> Property hooks and asymmetric visibility in PHP 8.4, and how these additions will change the way we model classes.

- Published: 2024-11-03
- Category: Languages
- Tags: PHP
- Reading time: 3 min read
- Source: https://www.muhammetsafak.com.tr/en/blog/php-8-4-property-hooks-and-asymmetric-visibility/
- Language: en-US
- Author: Muhammet Şafak

---
PHP 8.4 is releasing at the end of this month. I'm already looking at it on beta before the stable release, because 8.4 is one of the most noteworthy versions in recent years: it brings two additions that will genuinely change the way we model class properties.

## Property hooks

Until now, whenever we wanted to intercept a property being read or written, we'd write getter/setter methods. This turned even a simple field into a pair of methods and bloated the class.

PHP 8.4 introduces **property hooks**: you can attach logic directly to a property that runs when it is read or written:

```php
class Kullanici
{
    public string $tamAd {
        get => $this->ad . ' ' . $this->soyad;
    }

    public string $eposta {
        set => strtolower(trim($value));
    }

    public function __construct(
        private string $ad,
        private string $soyad,
    ) {}
}
```

Here `$tamAd` is never stored; it is computed on read. `$eposta` is normalized on write. Code from the outside still sees a plain property — `$kullanici->eposta` — but logic can be injected. Something I used to solve with getter/setter pairs is now expressed by the property itself.

### Hooks working alongside interfaces

The area where property hooks will make the biggest difference is interfaces. Hook signatures can be declared in interfaces:

```php
interface Adlandirilabilir
{
    public string $tamAd { get; }
}
```

This moves the contract — "this class must have a readable `$tamAd` property" — directly into the interface. Whether the implementing class stores a real field or computes it via a `get` hook is irrelevant, as long as the contract is fulfilled. I used to handle this with a doc comment or by forcing a `getName()` method; now the language understands it.

### A pitfall to watch out for

The `set` hook does not write the value to the property — that is your responsibility. If you don't write `$this->eposta = $value` inside the hook, the property is never assigned. This can catch someone coming from C# or Kotlin properties off-guard:

```php
public string $eposta {
    set {
        // Normalize AND don't forget to STORE:
        $this->eposta = strtolower(trim($value));
    }
}
```

With the short arrow syntax (`set => strtolower(trim($value))`), the assignment is automatic; the explicit write is only required when you use the block form `{ set { ... } }`.

## Asymmetric visibility

The second addition is **asymmetric visibility**. You can bind separate visibility levels to reading and writing a property:

```php
class Siparis
{
    public private(set) string $durum = 'bekliyor';
}
```

Here `$durum` is readable from outside but can only be changed from within the class. Previously you had to make the property `private` and write a separate getter method for this. Now the intent — "everyone can see it, nobody can change it from outside" — is expressed in a single line.

`protected(set)` is also available, meaning subclasses can write but it is closed to the outside:

```php
class Temel
{
    public protected(set) int $versiyon = 1;
}

class Alt extends Temel
{
    public function yuksel(): void
    {
        $this->versiyon++; // Valid
    }
}

$obj = new Alt();
echo $obj->versiyon; // 1 — readable
$obj->versiyon = 5;  // Error! Cannot write from outside
```

This is the more flexible sibling of the [`readonly` properties introduced in PHP 8.1](/en/blog/php-8-1-safer-domain-models-with-enums-and-readonly/). `readonly` can be written once and never changed again — truly immutable. Asymmetric visibility gives the guarantee of "write from inside as many times as you like, nobody outside can touch it." The two address different needs.

## What these two features say together

Property hooks and asymmetric visibility point in the same direction: **keep using properties like simple properties, but retain control when you need it.** For years, whenever I wanted to protect or compute a field I had to bloat the class with methods; calling code then saw methods instead of plain fields. 8.4 removes that trade-off.

I feel this most clearly when writing value objects. Previously in a class like `Money` I'd write chains of getters: `getAmount()`, `getCurrency()`, `getFormatted()`. Now it's `$money->amount`, `$money->currency`, `$money->formatted` — no methods to read, but control in place for writes.

When the release lands, the first thing I'll do is revisit the pile of getters in my value objects. Most of them will collapse into a simple property hook. Less code, same guarantee — that's exactly what I want from a language release.

## Comparison with readonly

It's worth drawing a clear distinction between `readonly` properties from PHP 8.1 and asymmetric visibility with property hooks. A `readonly` property can only be assigned once — outside the constructor — and can never be changed afterwards; it is fully immutable. `private(set)` with asymmetric visibility gives the guarantee of "change it from inside as many times as you like, but nobody outside can touch it." The two serve different needs: `readonly` is better suited for value objects, while `private(set)` is the right tool when you want state to be mutable only from within the class in a controlled way. Using one in place of the other without understanding this distinction will back you into a corner the moment a change is required.
