These 8.4 features remove most getter and setter boilerplate while keeping encapsulation.
Property hooks (PHP 8.4) attach get and set logic directly to a property, so callers use plain property syntax while the class keeps control:
public string $email {
set(string $value) { $this->email = strtolower(trim($value)); }
}- A
sethook runs on every write, including in the constructor. Inside it,$this->emailrefers to the backing value. - A
gethook computes the value on read. A property whose hooks never touch the backing value is virtual: it takes no memory, likepublic string $fullName { get => "$this->first $this->last"; }. - The short form
set => strtolower($value);assigns the expression to the backing value. - Interfaces and abstract classes can declare properties with hooks, e.g.
public string $name { get; }, so a contract can require a readable property.
Asymmetric visibility lets reading and writing have different visibility: public private(set) int $balance = 0; is readable everywhere but writable only inside the class. protected(set) allows subclasses to write. Unlike readonly, the class can change the value as often as it likes.
Rules of thumb: use readonly for true value objects, private(set) for entities whose state changes through methods, and hooks for normalisation or derived values. Hooks cannot be combined with readonly.
Example
<?php // PHP 8.4
final class Account
{
public private(set) int $balance = 0;
public string $owner {
set(string $value) {
if ($value === '') throw new ValueError('owner required');
$this->owner = ucwords(strtolower($value));
}
}
public string $label {
get => "{$this->owner} ({$this->balance})"; // virtual property
}
public function __construct(string $owner) { $this->owner = $owner; }
public function deposit(int $amount): void { $this->balance += $amount; }
}
$a = new Account('ADA lovelace');
$a->deposit(50);
echo $a->label, "\n"; // Ada Lovelace (50)
try { $a->balance = 1000; } catch (Error $e) { echo "read-only from outside\n"; }Remember
- set hooks normalise or validate on every write; get hooks compute on read
- A property whose hooks never use the backing value is virtual
- public private(set) = readable everywhere, writable only inside the class
Watch out for
- Reaching for hooks on a readonly property; the two can't be combined.