0

PHP 8.4 property hooks — get/set on class properties

Advanced5 min read·php-02-018
interviewcompare

Concept

Property hooks (PHP 8.4) let you attach get and set logic directly to a class property — eliminating the boilerplate getter/setter pattern while keeping properties as first-class citizens. Before PHP 8.4, computed or validated properties required private backing fields plus public methods. Now the logic lives inline with the property declaration.

A get hook runs when the property is read. A set hook runs when the property is written to, receiving the incoming value as its parameter. You can define either hook independently — a property can have only get, only set, or both. If you omit set, the property becomes effectively read-only from outside. If you omit get, reading returns the backing value directly.

The backing value is accessed inside hooks via $this->propName — but only after the first set has run. Hooks can call other methods, throw exceptions, transform values, or fire events. The backing storage is managed by the engine; there is no separate private field.

Compared to magic __get/__set: property hooks are declared per-property, are fully typed, support IDE autocompletion, and avoid the dynamic dispatch overhead and isset() edge cases that plague __get/__set. Laravel's Eloquent still uses __get/__set for backwards compatibility, but new code should prefer hooks.

Code Example

php
<?php
declare(strict_types=1);

class Product
{
    // get + set hooks — validated setter, computed getter
    public float $price {
        get => round($this->price, 2);
        set(float $value) {
            if ($value < 0) {
                throw new \InvalidArgumentException("Price cannot be negative");
            }
            $this->price = $value;
        }
    }

    // set-only hook — normalizes on write, reads directly
    public string $slug {
        set(string $value) {
            $this->slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $value));
        }
    }

    // get-only hook — computed, no backing storage written
    public string $label {
        get => "{$this->slug} — \${$this->price}";
    }

    public function __construct(string $name, float $price)
    {
        $this->slug = $name;   // triggers set hook → slugifies
        $this->price = $price; // triggers set hook → validates
    }
}

$p = new Product('My Great Product!', 9.999);
echo $p->slug;   // "my-great-product"
echo $p->price;  // 10.0 (rounded by get hook)
echo $p->label;  // "my-great-product — $10"

// $p->price = -5; // throws InvalidArgumentException

Interview Q&A

Q: What problem do PHP 8.4 property hooks solve, and how do they differ from traditional getter/setter methods?

Traditional getters/setters require a private backing field, two public methods, and break property-access syntax — callers must remember $obj->getPrice() vs $obj->price. Property hooks attach validation and computation logic directly to the property declaration, preserving $obj->price access syntax while adding behavior. This means interface contracts can still expose properties, not just methods. They also differ from __get/__set by being statically declared per-property, which enables IDE autocompletion, type checking, and avoids the dynamic dispatch and isset() bugs of magic methods.


Q: What is the backing value in a property hook, and when is it initialized?

The backing value is the engine-managed storage for a hooked property. Inside a set hook, assigning $this->propName = $value writes to the backing value. Inside a get hook, reading $this->propName reads the backing value. The backing value is uninitialized until the first set runs — reading a get-only property before a set hook has written it will throw an "uninitialized property" error. If you define only a get hook with no backing write, the property has no persistent backing storage at all (it is purely computed on each read).


Q: Can property hooks be used in interfaces and abstract classes?

Yes — interfaces can declare properties with hooks (or just with get / set constraints), and implementing classes must honor those hook signatures. An interface can declare public string $name { get; } to require a readable $name, and the implementing class can satisfy it with either a plain property or one with a get hook. Abstract classes can declare abstract property hooks, which subclasses must implement. This elevates properties to the same contractual status as methods in interfaces.