Property Hooks (PHP 8.4)
debt(d7/e3/b3/t7)
Closest to 'only careful code review or runtime testing' (d7), PHPStan may catch some hook misuse but infinite recursion or silently-discarded set values typically surface only at runtime or in review.
Closest to 'simple parameterised fix' (e3), fixing a hook recursion or missing assignment is localized to the property declaration, slightly more than a one-liner because the hook pattern must be corrected.
Closest to 'localised tax' (b3), property hooks are scoped to individual classes/properties; the choice doesn't propagate system-wide though it does shape class API conventions.
Closest to 'serious trap' (t7), the misconception that hooks replace all getters/setters plus the infinite-recursion gotcha (accessing $this->property inside its own hook) contradicts how getters work in other languages.
Also Known As
TL;DR
Explanation
PHP 8.4 property hooks: get hook runs when the property is read; set hook runs when written. Short form: public string $name { set => ucfirst($value); } applies a transformation on every assignment. Long form with full validation: public int $age { set { if ($value < 0) throw new ValueError(); $this->age = $value; } }. Hooks can be abstract in interfaces. Work with constructor promotion. $value in set holds the incoming value. Hooks do not replace complex computed properties — use methods for those.
Common Misconception
Why It Matters
Common Mistakes
- Infinite recursion in a get hook that reads the same property — access $this->fieldName not $this->property
- Set hook that forgets to assign $this->property = $value — the incoming value is silently discarded
- Complex business logic in hooks — extract to methods for clarity
- Hooks on static properties — not supported in PHP 8.4
Code Examples
// PHP 8.3 boilerplate — three members for one concept:
class User {
private string $_email;
public function getEmail(): string { return $this->_email; }
public function setEmail(string $email): void {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) throw new ValueError();
$this->_email = strtolower($email);
}
}
// PHP 8.4 property hooks — co-located logic:
class User {
public string $email {
set {
if (!filter_var($value, FILTER_VALIDATE_EMAIL))
throw new ValueError('Invalid email: ' . $value);
$this->email = strtolower($value); // Assign to $this->email
}
}
// Short hook for simple transformation:
public string $name { set => ucwords(strtolower($value)); }
}
$user = new User();
$user->email = 'ALICE@EXAMPLE.COM'; // Validated + lowercased automatically
$user->name = 'john doe'; // Becomes 'John Doe' automatically