PHP 8.4 property hooks — get/set on class properties
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
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 InvalidArgumentExceptionInterview 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.