Property Hooks (PHP 8.4)

Ma Mariya Vinciya Updated 21 Oct 2026
6 min read ·Lesson 170 of 174
In this post

What Are Property Hooks? In simple terms: Property hooks let you attach small pieces of code to a property that run automatically when the property is read or written . You can normalize an email addr

What Are Property Hooks?

In simple terms: Property hooks let you attach small pieces of code to a property that run automatically when the property is read or written. You can normalize an email address whenever it is assigned, reject invalid prices, or provide a calculated property such as fullName — while code using the object still writes simple property access like $user->email = '...'.

Technically: PHP 8.4 introduced get and set hooks declared in a block after a property declaration. A set hook runs on assignment and receives the new value; a get hook runs on read and returns the value. Inside a hook, $this->propertyName accesses the property's underlying backing value. A property whose hooks never use the backing value is virtual: it stores nothing and is computed on access.

Why Hooks Were Added

Before PHP 8.4, validating or computing property values required one of these approaches:

ApproachDrawback
Private property + getEmail() / setEmail() methodsBoilerplate for every property; callers must use methods
Magic __get() / __set()Slow, invisible to IDEs and static analysis, easy to get wrong
Public property without checksNo validation at all

 

Hooks keep the simple syntax of public properties while adding the control of methods. A class can start with a plain public property and add a hook later without changing any calling code.


A set Hook: Validation and Normalization

<?php
final class User
{
    public string $email {
        set(string $value) {
            $value = strtolower(trim($value));

            if (filter_var($value, FILTER_VALIDATE_EMAIL) === false) {
                throw new InvalidArgumentException("Invalid email: $value");
            }

            $this->email = $value;
        }
    }

    public function __construct(public string $name, string $email)
    {
        $this->email = $email;   // runs the set hook
    }
}

$user = new User('Asha', '  Asha@Example.COM ');
echo $user->email . "\n";

$user->email = 'asha.k@gocourse.in';
echo $user->email . "\n";

try {
    $user->email = 'not-an-email';
} catch (InvalidArgumentException $e) {
    echo $e->getMessage() . "\n";
}

echo $user->email . "\n";

 

Output

asha@example.com
asha.k@gocourse.in
Invalid email: not-an-email
asha.k@gocourse.in

 

How It Works

  1. public string $email { set(...) { ... } } declares a public property with a set hook.
  2. Every assignment — in the constructor or from outside — calls the hook with the new value as $value.
  3. The hook normalizes the value (trim, lowercase) and validates it. Invalid values throw an exception, so the property keeps its previous valid value.
  4. $this->email = $value; inside the hook writes the backing value directly; it does not call the hook again.
  5. Reading $user->email has no get hook, so it simply returns the stored value.

The set hook's parameter list can be omitted; the new value is then available as $value automatically: set { ... }.


Short Hook Syntax

For simple transformations, the arrow form is more concise:

<?php
final class Product
{
    public string $sku {
        set => strtoupper(trim($value));   // the result is stored
    }

    public function __construct(string $sku)
    {
        $this->sku = $sku;
    }
}

$product = new Product('  pen-01 ');
echo $product->sku . "\n";

 

Output

PEN-01

 

set => expression; stores the result of the expression in the backing value. Similarly, get => expression; returns the result of an expression.


get Hooks and Virtual Properties

A get hook that does not use the property's own backing value creates a virtual property — a computed value that takes no storage:

<?php
final class Product
{
    public int $pricePaise {
        set {
            if ($value < 0) {
                throw new InvalidArgumentException('Price cannot be negative.');
            }
            $this->pricePaise = $value;
        }
    }

    public string $formattedPrice {
        get => '₹' . number_format($this->pricePaise / 100, 2);
    }

    public function __construct(public string $name, int $pricePaise)
    {
        $this->pricePaise = $pricePaise;
    }
}

$lamp = new Product('Desk Lamp', 129900);
echo $lamp->formattedPrice . "\n";

$lamp->pricePaise = 99900;
echo $lamp->formattedPrice . "\n";

try {
    $lamp->formattedPrice = '₹1';
} catch (Error $e) {
    echo $e->getMessage() . "\n";
}

 

Output

₹1,299.00
₹999.00
Property Product::$formattedPrice is read-only

 

  • formattedPrice is calculated from pricePaise on every read, so it can never be out of date.
  • It has only a get hook and no backing value, so it cannot be written; assignment throws an Error.
  • Virtual properties appear like normal properties to callers, which is convenient in templates and serializers: {$product->formattedPrice}.

Combining get and set

Both hooks can be defined on one property:

public string $phone {
    get => '+91 ' . substr($this->phone, 0, 5) . ' ' . substr($this->phone, 5);
    set => preg_replace('/\D+/', '', $value);
}

 

The phone number is stored as digits only and displayed formatted.


Hooks in Interfaces and Abstract Classes

PHP 8.4 also allows properties in interfaces, declaring which operations implementations must support:

<?php
interface HasDisplayName
{
    public string $displayName { get; }
}

final class Customer implements HasDisplayName
{
    public function __construct(private string $firstName, private string $lastName) {}

    public string $displayName {
        get => "{$this->firstName} {$this->lastName}";
    }
}

final class Company implements HasDisplayName
{
    public function __construct(public string $displayName) {}
}

foreach ([new Customer('Asha', 'Kumar'), new Company('GoCourse Pvt Ltd')] as $party) {
    echo $party->displayName . "\n";
}

 

Output

Asha Kumar
GoCourse Pvt Ltd

 

{ get; } requires a readable property. Customer provides it as a virtual property; Company uses a plain public property (here, promoted). Code that depends on HasDisplayName works with both. Abstract classes can similarly declare abstract properties with hook requirements.


Rules and Limitations

RuleDetail
Hooks and readonly cannot be combinedUse asymmetric visibility or a set hook instead (see Asymmetric Visibility (PHP 8.4))
Static properties cannot have hooksHooks apply to object properties only
The set parameter type must accept the property typeIt may be wider, allowing conversion inside the hook
Virtual properties have no storageThey cannot be written unless a set hook is defined
Promoted constructor properties can have hookspublic function __construct(public string $sku { set => strtoupper($value); })
Indirect modification of hooked array properties$obj->items[] = $x is not allowed for properties with a set hook

 


Hooks vs Methods

Use a Hook WhenUse a Method When
The operation is a natural property assignment or readThe operation is an action with business meaning (markAsPaid())
Logic is short: normalization, validation, simple computationLogic is complex, slow, or has side effects (database, network)
You want to add validation to an existing public property without changing callersThe operation needs several parameters

 

Property access should remain cheap and free of surprising side effects; heavy work belongs in methods.


Common Mistakes

Assigning Through the Property Inside Its Own Hook Expecting Recursion

Effect: Developers sometimes think $this->email = $value inside the set hook calls the hook again.

Fix: Understand that inside its own hooks, $this->property accesses the backing value directly.

Expensive get Hooks

Effect: Every read repeats a slow computation.

Fix: Keep hooks fast, or use explicit methods with caching.

Combining Hooks With readonly

Effect: Compile error.

Fix: Use public private(set) with a set hook, or a readonly property without hooks.

Putting Business Workflows in Hooks

Effect: Hidden side effects on simple assignments.

Fix: Use named methods for actions.


Best Practices

  • Use set hooks to validate and normalize property values at the moment they are assigned.
  • Use get-only virtual properties for derived values.
  • Declare required properties in interfaces with { get; } or { get; set; }.
  • Keep hooks short, fast, and free of side effects.
  • Start with plain public properties and add hooks when rules appear — without changing calling code.

Official reference: https\://www.php.net/manual/en/language.oop5.property-hooks.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning MySQL Database MySQL Connect MySQL Create DB MySQL Create Table MySQL Insert Data MySQL Get Last ID MySQL Insert Multiple MySQL Prepared MySQL Select Data MySQL Where Debugging PHP Project: Image Upload Gallery $_SESSION $_ENV PHP RegEx RegEx Patterns Heredoc and Nowdoc RegEx Modifiers preg_match preg_replace preg_split PHP RegEx Functions Nullable Types Mixed and Never Types Nullsafe Operator PHP Attributes PHP Fibers Readonly Classes Property Hooks (PHP 8.4) Asymmetric Visibility (PHP 8.4) Reflection API SPL Data Structures Composer
Ask about this post
AI Ask about this post

Ask questions about Property Hooks (PHP 8.4) and get answers drawn from it.

Signed-in readers only.