readonly Properties and Constructor Promotion
Promote constructor params into properties in one line, and mark them readonly so an object becomes immutable after construction.
Two modern features that work beautifully together. Constructor property promotion lets you declare and assign a property right in the constructor signature — no more repetitive $this->x = $x; boilerplate. Add readonly and the property can be set exactly once, during construction, then never changed.
Easy example
<?php
class Point {
public function __construct(
public readonly int $x,
public readonly int $y,
) {}
}
$p = new Point(3, 4);
echo "$p->x, $p->y";
// $p->x = 9; // Error: cannot modify readonly propertyPHP 8.2 extended this to whole readonly class declarations, and 8.3 added readonly to anonymous classes — immutability with almost no ceremony.
Example
When to use it
- Use constructor promotion with readonly to create immutable value objects like Money or EmailAddress.
- Prevent accidental mutation of a configuration object after boot by declaring all its properties readonly.
- Model a domain event as a readonly class so its data can never change after it is created.
More examples
Constructor promotion with readonly
Promoted readonly properties are set once in the constructor and can never be reassigned, making Point immutable.
<?php
class Point {
public function __construct(
public readonly float $x,
public readonly float $y
) {}
public function distanceTo(self $other): float {
return sqrt(($this->x - $other->x) ** 2 + ($this->y - $other->y) ** 2);
}
}
$a = new Point(0, 0);
$b = new Point(3, 4);
echo $a->distanceTo($b); // 5
// $a->x = 10; // Fatal: readonly propertyreadonly class (PHP 8.2)
A readonly class makes every property implicitly readonly, enforcing full immutability without per-property annotations.
<?php
readonly class Coordinate {
public function __construct(
public float $lat,
public float $lng
) {}
public function toArray(): array {
return ['lat' => $this->lat, 'lng' => $this->lng];
}
}
$c = new Coordinate(51.5074, -0.1278);
print_r($c->toArray());Wither pattern for immutable updates
The wither pattern returns a new instance with one field changed, giving readonly objects a safe 'mutation' API.
<?php
readonly class UserSettings {
public function __construct(
public string $theme = 'light',
public string $locale = 'en',
public bool $notifications = true
) {}
public function withTheme(string $theme): self {
return new self($theme, $this->locale, $this->notifications);
}
}
$settings = new UserSettings();
$dark = $settings->withTheme('dark');
echo $settings->theme; // light
echo $dark->theme; // dark
Discussion