PHP 8.4 introduces property hooks — get and set hooks that run when a property is read or written, without requiring explicit getter/setter methods. They bring computed properties (a property whose value is derived from other state) and validated properties (a property that enforces a constraint on write) into the language as first-class syntax, making value objects far more expressive.
Problem: PHP class properties that need computed or validated values — a price that must be positive, a slug that must be lowercase — require explicit getter and setter methods, creating boilerplate that obscures the intent of the value object.
Solution: Use PHP 8.4 Property Hooks to define get and set logic directly on the property declaration: public string $slug { set { $this->slug = strtolower($value); } }. The hook runs automatically on assignment, keeping validation co-located with the property. Interfaces can declare property hooks to enforce computed property contracts.
The examples below show get-only hooks (computed properties), set hooks with validation, combined get+set hooks for format normalisation, and a practical WordPress price value object using property hooks.
M_PI * $this->radius ** 2;
}
public float $circumference {
get => 2 * M_PI * $this->radius;
}
}
$c = new Circle();
$c->radius = 5.0;
echo $c->area; // 78.539... (computed, not stored)
echo $c->circumference; // 31.415...
// ── 2. Set hook: validated property ──────────────────────────────────────
class Post {
public string $title {
set {
if ( strlen( $value ) < 3 ) {
throw new \ValueError( 'Title must be at least 3 characters.' );
}
$this->title = sanitize_text_field( $value ); // write to backing storage
}
}
public int $status {
set {
$allowed = [ 0, 1, 2 ];
if ( ! in_array( $value, $allowed, true ) ) {
throw new \ValueError( "Invalid status: $value" );
}
$this->status = $value;
}
}
}
$post = new Post();
$post->title = ' Hello World '; // set hook sanitises: stored as "Hello World"
$post->status = 1;
// $post->status = 99; // throws ValueError
// ── 3. Combined get + set: format normalisation ───────────────────────────
class Email {
public string $address {
get => strtolower( $this->address ); // always return lowercase
set => filter_var( $value, FILTER_VALIDATE_EMAIL )
? strtolower( $value )
: throw new \ValueError( "Invalid email: $value" );
}
}
// ── 4. WordPress product price with hooks ────────────────────────────────
class WPProduct {
public float $price {
set {
if ( $value < 0 ) {
throw new \ValueError( 'Price cannot be negative.' );
}
$this->price = round( $value, 2 );
}
get => $this->price;
}
public string $formattedPrice {
get => '$' . number_format( $this->price, 2 );
}
public function __construct(
public readonly int $id,
public readonly string $name,
float $price,
) {
$this->price = $price; // triggers the set hook
}
}
NOTE: A property with only a get hook and no set hook is effectively read-only from outside the class but can be set in the constructor using $this->prop = value — inside the class the backing storage is always writable; to make a property truly immutable outside the constructor, combine a get hook with private(set) visibility.