PHP Property Hooks
last modified October 22, 2026
In this article, we show how to use property hooks in PHP. Property
hooks were introduced in PHP 8.4. They let us attach get and
set logic directly to a property declaration, so validation,
normalization, and computed values no longer require separate getter and setter
methods or the __get and __set magic methods.
The examples require PHP 8.4 or newer. A hook can be written as a block
(get { ... }) or, for a single expression, with the arrow syntax
(get => expression;).
Main Features of PHP Property Hooks
- get hook: runs when the property is read.
- set hook: runs when the property is assigned; the new value is in
$value. - Backed properties: hooks that use
$this->propwork with real storage. - Virtual properties: hooks that never touch
$this->propcompute the value, and no storage is allocated. - Interfaces: interfaces and abstract classes can require properties with
getand/orset.
Backed property with a set hook
A property hook is a block of code attached directly to a property
declaration. A set hook runs whenever a value is assigned to the
property. Inside the hook the incoming value is available as
$value.
<?php
class Product
{
public string $name {
set {
if (strlen($value) < 3) {
throw new InvalidArgumentException(
"Name must be at least 3 characters long.");
}
$this->name = ucfirst($value);
}
}
}
$product = new Product();
$product->name = "laptop";
echo $product->name, "\n";
try {
$product->name = "ab";
} catch (InvalidArgumentException $e) {
echo $e->getMessage(), "\n";
}
The name property is backed: the hook assigns to
$this->name, which refers to the real storage and does not call the
hook again. The hook validates the value and capitalizes it before storing it.
Note that no __set method and no private field are needed; the
property stays public and callers use plain assignment.
$ php set_hook.php Laptop Name must be at least 3 characters long.
The get hook
A get hook runs whenever the property is read. When the
hook consists of a single expression, we can use the short arrow syntax.
<?php
class User
{
public string $email {
get => strtolower($this->email);
}
public function __construct(string $email)
{
$this->email = $email;
}
}
$user = new User("John.Doe@Example.COM");
echo $user->email, "\n";
The email property has a get hook but no
set hook, so assignments store the value as is. Inside the hook,
$this->email reads the stored value directly without recursing. The
hook normalizes the e-mail address to lowercase each time it is read, while the
stored value remains unchanged.
$ php get_hook.php john.doe@example.com
Virtual properties
A property that has hooks but never reads or writes $this->prop
inside them is virtual: it has no storage. Virtual properties are
computed from other state.
<?php
class Person
{
public function __construct(
public string $firstName,
public string $lastName,
) {}
public string $fullName {
get => $this->firstName . ' ' . $this->lastName;
set {
[$this->firstName, $this->lastName] = explode(' ', $value, 2);
}
}
}
$p = new Person("John", "Doe");
echo $p->fullName, "\n";
$p->fullName = "Jane Roe";
echo $p->firstName, "\n";
echo $p->lastName, "\n";
The fullName property is virtual. Its get hook
builds the value from two real properties, and its set hook splits
an assigned string back into them. A virtual property that has only a
get hook is read-only; assigning to it raises an
Error.
$ php virtual.php John Doe Jane Roe
Constructor property promotion with hooks
Hooks can be combined with constructor promotion. The hooks are declared in a block after the parameter.
<?php
class Account
{
public function __construct(
public float $balance = 0.0 {
set {
if ($value < 0) {
throw new RangeException("Balance cannot be negative.");
}
$this->balance = round($value, 2);
}
},
) {}
}
$account = new Account(100);
echo $account->balance, "\n";
$account->balance = 99.567;
echo $account->balance, "\n";
try {
$account->balance = -5;
} catch (RangeException $e) {
echo $e->getMessage(), "\n";
}
The hook is attached to the promoted balance parameter. It is
applied to the value passed to the constructor and to later assignments. Here
it rejects negative values and rounds the balance to two decimal places.
$ php promotion.php 100 99.57 Balance cannot be negative.
Logging property changes
Hooks make it easy to observe a property without changing the code that uses it.
<?php
class Settings
{
public string $theme = 'light' {
set {
echo "Theme changed from {$this->theme} to $value\n";
$this->theme = $value;
}
}
}
$settings = new Settings();
$settings->theme = 'dark';
$settings->theme = 'solarized';
echo $settings->theme, "\n";
Every assignment to theme goes through the set
hook, which prints the old and new values before storing the new one. The
initial value in the declaration does not trigger the hook. Reading the
property uses the default behaviour because no get hook is
defined.
$ php logging.php Theme changed from light to dark Theme changed from dark to solarized solarized
Interfaces and abstract properties
Interfaces and abstract classes can declare properties together with the hooks that implementations must support.
<?php
interface Named
{
public string $name { get; }
}
class Dog implements Named
{
public function __construct(public string $name) {}
}
class Robot implements Named
{
public string $name {
get => 'RX-' . $this->id;
}
public function __construct(private int $id) {}
}
function greet(Named $n): void
{
echo "Hello, {$n->name}\n";
}
greet(new Dog("Rex"));
greet(new Robot(78));
The Named interface requires a readable name
property. Dog satisfies it with an ordinary public property, while
Robot provides a virtual property computed by a get
hook. Callers do not care which implementation they receive.
$ php interface.php Hello, Rex Hello, RX-78
Asymmetric visibility
PHP 8.4 also introduces asymmetric visibility, which complements hooks: a property can be publicly readable but writable only from inside the class.
<?php
class Counter
{
public private(set) int $count = 0;
public function increment(): void
{
$this->count++;
}
}
$c = new Counter();
$c->increment();
$c->increment();
echo $c->count, "\n";
try {
$c->count = 10;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
The private(set) modifier restricts writes to the class scope
while reads stay public. This is often a simpler alternative to a hook when we
only need to prevent outside modification.
$ php asymmetric.php 2 Cannot modify private(set) property Counter::$count from global scope
Limitations
- Hooks cannot be used on
staticproperties. - Hooks cannot be combined with
readonlyproperties. - A virtual property cannot have a default value.
- A hook that reads or writes its own property (
$this->prop) works on the backing value; this is the only place where the hook is bypassed.
Source
PHP Property Hooks - Documentation
In this article, we have shown how to use property hooks in PHP to define custom behavior for property access and modification. Property hooks are a powerful tool for enforcing validation, logging, and encapsulating property logic.
Author
List all PHP tutorials.