Using PHP 8.2 Readonly Properties for Safer Immutable Data Models
Learn how readonly properties in PHP 8.2 enforce immutability, reduce boilerplate, and what trade‑offs to consider before adopting them.
02 Dec 2025, 04:30 UTC

The problem: accidental mutation of data objects
In many PHP applications value objects or DTOs are passed around. If a property can be changed after creation, a bug in one part of the code can silently corrupt data that other services rely on.
What readonly properties give you
Starting with PHP 8.2 you can mark a typed property with the readonly keyword. The property must be assigned exactly once – either at declaration or in the constructor – and after that any attempt to write to it throws a TypeError. This makes the intent of immutability explicit, removes the need for getter‑only boilerplate, and lets static analysis tools spot illegal writes.
Worked example: a Money value object
<?php
declare(strict_types=1);
final class Money
{
public function __construct(
public readonly float $amount,
public readonly string $currency
) {}
}
// Usage
$price = new Money(15.99, 'EUR');
echo "{$price->amount} {$price->currency}" . PHP_EOL; // 15.99 EUR
// Attempt to modify – will fail
$price->amount = 20.00; // TypeError: Cannot modify readonly property Money::$amount
?>
Run the script with php -d opcache.enable_cli=1 money.php. The first echo works; the second line produces a fatal TypeError. Changing the property to a regular (non‑readonly) version and re‑running shows no error, demonstrating the enforcement.
Trade‑offs and limitations
- Initialization must happen on every code path; conditional assignment like
if ($flag) { $this->amount = 1; }is not allowed and results in a fatal error if any path skips it. - Readonly properties cannot be
unsetor reassigned via reflection, which can break legacy code that relies onProperty::setValueor__set. - Performance is comparable to regular properties; OPcache can optimize accesses, but you lose the ability to use lazy initialization patterns that depend on writing after construction.
When to use them – actionable closing
If you are on PHP 8.2 or newer and you model data that should not change after creation – value objects, DTOs, configuration objects – replace mutable typed properties with readonly ones. Verify the version, run the small test above, and ensure your constructor assigns the property on all paths. When you need conditional defaults, compute the value before calling the constructor instead of trying to assign inside it.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.