readonly + accessors
Stable DTOs and Value Objects without boilerplate
- Added in: readonly — PHP 8.1, accessors — PHP 8.4
- What it gives you: the ability to create objects whose data can't be changed by accident, plus a new syntax for controlling access to properties via get / set without separate methods.
What it's about
readonly makes a property immutable after initialization — the value can be set only once (usually in the constructor).
accessors let you add control over reading and writing properties without classic getters and setters — right in the class body.
Together they make a perfect combination for DTOs, VOs and configuration classes: the data is immutable and access is controlled.
The classic approach
class Product {
private string $name;
private float $price;
public function __construct(string $name, float $price)
{
$this->name = $name;
$this->price = $price;
}
public function getPrice(): float {
return $this->price;
}
public function setPrice(float $price): void {
if ($price < 0) {
throw new InvalidArgumentException('Price must be positive');
}
$this->price = $price;
}
}
It works, but most of this code exists only to provide basic encapsulation.
Now the same — the modern way
class Product {
public readonly string $name;
private float $price {
get => $this->price;
set => $this->price = max(0, $value);
}
public function __construct(string $name, float $price)
{
$this->name = $name;
$this->price = $price;
}
}
readonly guarantees the name is immutable, and the accessors automatically check the price on write.
One more example — a Value Object
class Coordinates {
public readonly float $lat {
get => round($this->lat, 6);
}
public readonly float $lng {
get => round($this->lng, 6);
}
public function __construct(float $lat, float $lng)
{
$this->lat = $lat;
$this->lng = $lng;
}
}
No changes after creation, only controlled access on read.
Where it really helps
DTO: API requests and responses;Value Object: Money, Price, GeoPoint, Range;Immutable configurations: service parameters, constant states.
Conclusion
readonly and accessors are a step towards real record classes in PHP. They let you write clean, predictable structures without a ton of boilerplate.
Today every DTO of mine is readonly.
← Previous: Enums · Whole series · Next: Attributes →
First published in Ukrainian in the Telegram channel “Created to die” on November 30, 2025.