Home PHP PHP Typed Properties, Readonly Classes & Property Hooks: Modern OOP
Intermediate 3 min · July 13, 2026

PHP Typed Properties, Readonly Classes & Property Hooks: Modern OOP

Master PHP typed properties, readonly classes, and property hooks.

N
Naren Founder & Principal Engineer

20+ years shipping production PHP systems at scale. Written from production experience, not tutorials.

Follow
Production
production tested
July 13, 2026
last updated
2,073
articles · all by Naren
Before you start⏱ 15-20 min read
  • Basic PHP syntax and OOP concepts
  • Familiarity with classes and objects
  • PHP 7.4+ installed (8.4 for property hooks)
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Typed properties enforce type at runtime, preventing invalid data.
  • Readonly properties can only be set once, typically in constructor.
  • Property hooks (PHP 8.4) allow custom get/set logic without methods.
  • Combining these features leads to safer, more expressive code.
✦ Definition~90s read
What is PHP Typed Properties, Readonly Classes, and Property Hooks?

PHP typed properties, readonly classes, and property hooks are language features that let you enforce data types, immutability, and custom access logic directly on class properties.

Think of a property like a labeled box.
Plain-English First

Think of a property like a labeled box. Typed properties say 'only put numbers here'. Readonly says 'once you put something, you can't change it'. Property hooks are like a smart box that runs a check or transformation whenever you put something in or take something out.

In modern PHP development, writing robust and maintainable code is paramount. One of the biggest leaps in that direction came with PHP 7.4's typed properties, which allow you to declare the exact type of data a class property can hold. No more guessing whether $user->name is a string or an object – the language enforces it at runtime. PHP 8.1 introduced readonly properties, which can only be assigned once, typically in the constructor, making immutable objects a breeze. And with PHP 8.4, property hooks are on the horizon, letting you add get/set logic directly on properties without writing separate methods. Together, these features transform how you design classes, making them more self-documenting, less error-prone, and easier to reason about. In this tutorial, you'll learn each feature inside out, see production-ready examples, and discover how to avoid common pitfalls. Whether you're building a simple DTO or a complex domain model, these tools will become your new best friends.

Typed Properties: The Basics

Typed properties allow you to declare the type of a class property. Supported types include scalar types (int, float, string, bool), arrays, iterable, object, mixed, and nullable types (prefixed with ?). You can also use class/interface names. Once declared, PHP will enforce that any value assigned to the property matches the type, throwing a TypeError if not. This catches bugs early and makes your code self-documenting.

Example: A simple User class with typed properties.

typed_properties.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

class User {
    public int $id;
    public string $name;
    public ?string $email = null;
    public array $roles = [];
}

$user = new User();
$user->id = 1;
$user->name = 'Alice';
$user->email = 'alice@example.com';
$user->roles = ['admin', 'editor'];

// This would throw a TypeError:
// $user->id = 'not an integer';
💡Uninitialized Properties
📊 Production Insight
In production, typed properties are especially valuable for DTOs and entities. They prevent silent data corruption from malformed API responses or database records.
🎯 Key Takeaway
Typed properties enforce type safety at runtime, reducing bugs from unexpected data types.

Nullable and Union Types

Sometimes a property can be null or multiple types. Use nullable types (e.g., ?string) or union types (e.g., int|string). Union types were introduced in PHP 8.0. They allow a property to accept multiple types, but be careful: too many union types can indicate a design flaw.

Example: A nullable email and a union type for a mixed value.

nullable_union.phpPHP
1
2
3
4
5
6
7
8
9
10
11
<?php

class Product {
    public ?string $description = null;
    public int|string $identifier;
}

$product = new Product();
$product->identifier = 123; // int
$product->identifier = 'SKU-456'; // also valid
// $product->identifier = 12.5; // TypeError: float not allowed
⚠ Avoid Overusing Union Types
📊 Production Insight
When dealing with external APIs, union types can model fields that may be a string or an integer (e.g., IDs). Always validate and normalize early.
🎯 Key Takeaway
Nullable and union types provide flexibility while still enforcing constraints.

Readonly Properties: Immutability Made Easy

Readonly properties (PHP 8.1) can only be assigned once, typically in the constructor. After initialization, any attempt to modify them throws an Error. This is perfect for value objects, DTOs, and configuration objects. Readonly properties can be typed and can have default values. They cannot be unset.

Example: A readonly configuration class.

readonly_properties.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

class AppConfig {
    public readonly string $appName;
    public readonly string $version;

    public function __construct(string $appName, string $version) {
        $this->appName = $appName;
        $this->version = $version;
    }
}

$config = new AppConfig('MyApp', '1.0.0');
echo $config->appName; // MyApp
// $config->appName = 'NewName'; // Error: Cannot modify readonly property
Output
MyApp
🔥Readonly Classes
📊 Production Insight
Use readonly for any property that should never change after object creation, such as IDs, timestamps, or configuration values. This prevents accidental mutations in complex codebases.
🎯 Key Takeaway
Readonly properties enforce immutability, making your code safer and easier to reason about.

Property Hooks: Custom Get/Set Logic (PHP 8.4)

Property hooks, introduced in PHP 8.4, allow you to define custom get and set logic directly on a property, without writing separate getter and setter methods. This makes your code more concise and readable. The syntax uses get and set keywords inside the property declaration. The set hook receives the value to be assigned, and you can use $this->property to access the backing value.

Example: A property that always trims strings on set.

property_hooks.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

class User {
    public string $name {
        set => trim($value);
    }

    public string $email {
        set {
            if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
                throw new InvalidArgumentException('Invalid email');
            }
            $this->email = $value;
        }
    }
}

$user = new User();
$user->name = '  Alice  ';
echo $user->name; // 'Alice'

$user->email = 'alice@example.com'; // OK
// $user->email = 'not-an-email'; // throws InvalidArgumentException
Output
Alice
⚠ Backing Value Access
📊 Production Insight
Use property hooks for lightweight validation or normalization. For complex business logic, consider a separate method or a value object.
🎯 Key Takeaway
Property hooks allow you to encapsulate validation and transformation logic directly on the property, reducing boilerplate.

Combining Typed, Readonly, and Hooks

You can combine all three features for maximum safety and expressiveness. For example, a readonly property with a typed declaration and a get hook that computes a derived value. However, readonly properties cannot have set hooks because they are immutable. You can have a get hook on a readonly property.

Example: A readonly property that formats a date.

combined.phpPHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

class Event {
    public readonly DateTimeImmutable $date;

    public string $formattedDate {
        get => $this->date->format('Y-m-d H:i:s');
    }

    public function __construct(DateTimeImmutable $date) {
        $this->date = $date;
    }
}

$event = new Event(new DateTimeImmutable('2025-01-15 10:30:00'));
echo $event->formattedDate; // 2025-01-15 10:30:00
// $event->date = new DateTimeImmutable(); // Error: readonly
Output
2025-01-15 10:30:00
💡Readonly + Get Hook
📊 Production Insight
In production, use readonly with typed properties for DTOs that represent external data. Add get hooks for formatting without exposing mutable internals.
🎯 Key Takeaway
Combining these features gives you fine-grained control over data integrity and presentation.

Common Pitfalls and Best Practices

  1. Uninitialized typed properties: Always initialize in constructor or with a default value.
  2. Readonly properties in inheritance: A readonly property can be redeclared in a child class only if it's not already initialized. Use readonly classes to avoid issues.
  3. Property hooks performance: Hooks add a small overhead. For hot paths, consider using methods.
  4. Type widening: You cannot widen a property type in a subclass (e.g., from int to int|string).
  5. Avoid side effects in get hooks: They should be idempotent and not modify state.

Example: A common mistake – forgetting to initialize a typed property.

pitfall.phpPHP
1
2
3
4
5
6
7
8
<?php

class User {
    public string $name; // no default, not initialized in constructor
}

$user = new User();
echo $user->name; // Error: Typed property User::$name must not be accessed before initialization
⚠ Always Initialize Typed Properties
📊 Production Insight
In production, use constructor promotion to initialize typed properties concisely. For example: public function __construct(public readonly string $name) {}
🎯 Key Takeaway
Follow best practices to avoid runtime errors and maintain clean code.
● Production incidentPOST-MORTEMseverity: high

The Silent Data Corruption: A Typed Property Nightmare

Symptom
Customers were charged negative amounts, causing accounting chaos.
Assumption
The developer assumed that form validation would catch invalid inputs before they reached the model.
Root cause
The amount property was untyped, allowing a string ' -50 ' to be stored and later used in calculations without proper casting.
Fix
Declared public float $amount; and added a setter hook (in PHP 8.4) to sanitize input.
Key lesson
  • Always use typed properties for domain-critical data.
  • Never trust external input; enforce types at the boundary.
  • Use readonly for values that should never change after construction.
  • Property hooks can centralize validation logic.
Production debug guideSymptom to Action3 entries
Symptom · 01
TypeError: must be of type int, string given
Fix
Check the caller: are you passing a string where an int is expected? Use var_dump on the input.
Symptom · 02
Cannot modify readonly property
Fix
Look for any assignment outside the constructor or a method that tries to set the property again. Use readonly only for truly immutable values.
Symptom · 03
Property hook not called
Fix
Ensure you're using PHP 8.4+ and the property is declared with set or get hooks. Check for typos in hook names.
★ Quick Debug Cheat SheetCommon typed property issues and immediate fixes.
TypeError on assignment
Immediate action
Check the value type with gettype()
Commands
var_dump($value);
echo gettype($value);
Fix now
Cast the value: (int) $value
Readonly property modified+
Immediate action
Search for all assignments to that property
Commands
grep -rn '->propertyName =' src/
Check constructor and any __set methods
Fix now
Remove the assignment or make the property not readonly
Property hook not firing+
Immediate action
Check PHP version (>=8.4)
Commands
php -v
Check property declaration syntax
Fix now
Ensure hook syntax is correct: public string $name { set => ... }
FeatureIntroducedPurposeCan have hooks
Typed propertiesPHP 7.4Enforce property typeYes (get/set)
Readonly propertiesPHP 8.1Immutable after constructionGet only
Property hooksPHP 8.4Custom get/set logicN/A
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
typed_properties.phpclass User {Typed Properties
nullable_union.phpclass Product {Nullable and Union Types
readonly_properties.phpclass AppConfig {Readonly Properties
property_hooks.phpclass User {Property Hooks
combined.phpclass Event {Combining Typed, Readonly, and Hooks
pitfall.phpclass User {Common Pitfalls and Best Practices

Key takeaways

1
Typed properties enforce type safety and reduce bugs.
2
Readonly properties enable immutability and safer code.
3
Property hooks provide concise get/set logic without extra methods.
4
Combine these features for robust, self-documenting classes.

Common mistakes to avoid

3 patterns
×

Accessing an uninitialized typed property

×

Trying to modify a readonly property outside constructor

×

Using property hooks for complex business logic

INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is the purpose of typed properties in PHP?
Q02SENIOR
How do readonly properties differ from final properties?
Q03SENIOR
Explain property hooks and how they differ from magic methods __get and ...
Q01 of 03JUNIOR

What is the purpose of typed properties in PHP?

ANSWER
Typed properties enforce type constraints at runtime, preventing invalid data from being stored and making code self-documenting.
FAQ · 3 QUESTIONS

Frequently Asked Questions

01
Can I use typed properties with PHP 7.4?
02
What happens if I assign a value of the wrong type to a typed property?
03
Can I make a property both readonly and have a set hook?
N
Naren Founder & Principal Engineer

20+ years shipping production PHP systems at scale. Written from production experience, not tutorials.

Follow
Verified
production tested
July 13, 2026
last updated
2,073
articles · all by Naren
🔥

That's Advanced PHP. Mark it forged?

3 min read · try the examples if you haven't

Previous
PHP DTOs and Value Objects with Readonly Classes
19 / 29 · Advanced PHP
Next
Introduction to Symfony Framework