PHPMiddleTechnical
Что такое readonly properties и классы в PHP 8.1/8.2?
Readonly properties (PHP 8.1) можно инициализировать один раз; повторная запись вызывает Error. Readonly classes (PHP 8.2) делают все свойства readonly автоматически, обеспечивая иммутабельные объекты.
Readonly properties (PHP 8.1)
Свойство с модификатором readonly можно присвоить ровно один раз — обычно в конструкторе. Любая последующая запись бросает Error: Cannot modify readonly property.
<?php
class Money
{
public readonly int $amount;
public readonly string $currency;
public function __construct(int $amount, string $currency)
{
if ($amount < 0) {
throw new InvalidArgumentException('Amount cannot be negative');
}
$this->amount = $amount;
$this->currency = strtoupper($currency);
}
}
$price = new Money(1000, 'usd');
echo $price->amount; // 1000
echo $price->currency; // USD
// $price->amount = 2000; // Error: Cannot modify readonly property Money::$amount
Constructor property promotion + readonly
<?php
class UserCreatedEvent
{
public function __construct(
public readonly int $userId,
public readonly string $email,
public readonly \DateTimeImmutable $occurredAt = new \DateTimeImmutable(),
) {}
}
$event = new UserCreatedEvent(userId: 42, email: 'alice@example.com');
echo $event->userId; // 42
Readonly classes (PHP 8.2)
Ключевое слово readonly перед class делает все объявленные свойства readonly автоматически. Нельзя добавлять нетипизированные свойства и динамические свойства.
<?php
readonly class Point
{
public function __construct(
public float $x,
public float $y,
public float $z = 0.0,
) {}
// Иммутабельный «вither»
public function withX(float $x): static
{
return new static($x, $this->y, $this->z);
}
}
$p1 = new Point(1.0, 2.0);
$p2 = $p1->withX(5.0);
echo $p1->x; // 1.0 — неизменён
echo $p2->x; // 5.0 — новый объект
Клонирование readonly объектов (PHP 8.3)
В PHP 8.3 clone with позволяет создавать изменённую копию readonly-объекта:
<?php
$p3 = clone $p1 with { x: 9.0 };
echo $p3->x; // 9.0
echo $p3->y; // 2.0 — скопировано
Ограничения
- Readonly свойства должны иметь явный тип —
public readonly $xвызывает синтаксическую ошибку. - Нельзя использовать
unset()для readonly свойства после инициализации. - Readonly class не может содержать статические свойства.
- Дочерний класс readonly class тоже должен быть объявлен readonly.
Подводные камни
- Попытка десериализации (unserialize) readonly-объекта:
__unserialize()пытается записать в свойства после конструктора — нужен явный__unserializeчерез reflection или специальный обходной путь. - Тестирование: создать mock или stub с readonly-свойствами через PHPUnit невозможно стандартными методами; нужны фабрики или клонирование.
- Гидрация из БД (Doctrine/Eloquent): ORM создаёт объект без конструктора и присваивает свойства напрямую через Reflection — это работает до инициализации, но нарушает семантику readonly при повторной гидрации.
- Readonly class нельзя использовать как mutable DTO: при изменении одного поля придётся пересоздавать весь объект.
- Статические анализаторы (PHPStan/Psalm) могут ложно сообщать об ошибках при использовании readonly с Reflection API.
Common mistakes
- Сводить readonly properties classes к названию метода без lifecycle и failure path.
- Игнорировать модель runtime: интерпретируемый runtime PHP 8.x, обычно запускаемый как отдельный запрос в FPM, CLI или worker-процессе.
- Не отделять validation, authorization, transaction boundary и business logic.
What the interviewer is testing
- Объясняет readonly properties classes через конкретную точку lifecycle в PHP.
- Приводит корректный минимальный пример без вымышленных методов или callbacks.
- Называет edge cases: пустые значения, ошибки, транзакции, безопасность или concurrency.