Readonly-свойства в PHP 8.1: руководство и практические советы

Readonly-свойства в PHP 8.1 дают встроенную иммутабельность для полей класса. Вы можете один раз задать значение в пределах области, где свойство определено, после чего любые записи вызовут ошибку. Это упрощает создание структур данных и DTO, снижает шаблонный код и повышает ясность намерений.
Быстрые ссылки
Пишем readonly-свойства
Ограничения и подводные камни
Когда использовать readonly
Дополнения: методики, чеклисты и советы по миграции
PHP 8.1 добавляет модификатор
readonlyдля свойств класса. Свойство, помеченное таким образом, можно присвоить только один раз. Попытка изменить значение readonly-свойства после инициализации приведет к ошибке времени выполнения.
Термин readonly здесь следует понимать как иммутабельность: значение можно задать, но нельзя изменить затем.
Пишем readonly-свойства
Чтобы сделать свойство только для чтения, добавьте модификатор
readonlyмежду модификатором доступа и подсказкой типа.
class Demo {
public string $Mutable;
public readonly string $Immutable;
public function __construct(
string $Mutable,
string $Immutable
) {
$this->Mutable = $Mutable;
$this->Immutable = $Immutable;
}
}$Mutable — обычное публичное свойство: его значение можно менять в любой момент, как внутри методов класса, так и извне.
$demo = new Demo('A', 'X');
$demo->Mutable = 'B';$Immutable ведет себя иначе: его можно читать, но попытка изменить значение вызовет
Error// Бросает Error
$demo->Immutable = 'Y';Модификатор
readonlyподдерживается и в promoted constructor properties:
class Demo {
public function __construct(
public readonly string $Immutable = 'foobar'
) {}
}Даже если у параметра конструктора указан стандартный аргумент, вы всё равно можете присвоить свойство вручную в теле конструктора. Promotion разворачивается в код, похожий на ранее показанный пример.
Ограничения и подводные камни
Readonly-свойства — это синтаксическое расширение, которое можно внедрять постепенно. Нет обратной несовместимости, их использование опционально. Однако есть важные ограничения, которые нужно учитывать.
- Нельзя задавать значение по умолчанию в объявлении readonly-свойства
class Demo {
protected readonly string $foo = 'bar';
}Это бы означало, что свойство уже инициализировано на этапе определения, и его нельзя изменить позже. Поэтому такие дефолты запрещены — используйте константу вместо этого:
class Demo {
const FOO = 'bar';
}- Только типизированные свойства
readonlyприменим только к типизированным свойствам. Неприменимо к не типизированным:
class Demo {
protected readonly $foo; // недопустимо
}Непривязанные свойства имеют неявное значение null. Типизированные свойства различают состояния «неинициализировано» и «null», поэтому readonly требует явного типа.
- Наследование и изменение модификатора
В дочерних классах нельзя добавлять или убирать
readonlyдля свойств, унаследованных от родителей. Разрешение подобных изменений могло бы нарушить гарантию родителя о неизменности данных и привести к побочным эффектам. RFC рассматривает readonly как намеренное ограничение, и оно сохраняется при наследовании.
- Область видимости и инициализация
Readonly-свойства можно устанавливать только в той области, где они определены. Это означает, что публичное свойство нельзя инициализировать извне, даже если оно ещё не было установлено:
class Demo {
public readonly string $foo;
}
$d = new Demo();
$d->foo = 'bar'; // незаконноИнициализация должна происходить внутри класса, который определил свойство. Благодаря этому поведение ближе к неизменяемым полям в других языках.
- Одинаковое поведение для всех записей
Модификатор не делает различий между внутренней и внешней записью. Нельзя, например, сделать свойство публично доступным, но записываемым только внутри класса. Это поведение может быть расширено в будущих спецификациях.
- Клонирование и неочевидная инициализация
Клонирование сохраняет состояние инициализированных свойств. Следующий код не сработает так, как могут ожидать некоторые разработчики:
class Demo {
public function __construct(
public string $foo
) {}
}
$d = new Demo('bar');
$d2 = clone $d;
$d2->foo = 'foobar'; // ошибка: свойство уже инициализировано при клонированииКлонирование выполняет неявную инициализацию свойств, поэтому первое присвоение после клонирования не считается “первым”.
Когда использовать readonly?
Readonly полезны для простых классов, которые представляют структуры данных: DTO, объекты запроса, ответов, структуры параметров. Такие объекты часто задумываются как неизменяемые после создания.
Раньше были два варианта:
- Публичные свойства — быстро, но позволяют внешние модификации.
- Защищённые свойства + геттеры — безопасно, но много шаблонного кода.
Примеры до PHP 8.1:
// Не лучший вариант — свойства могут быть изменены внешне
class UserCreationRequest {
public function __construct(
public string $Username,
public string $Password
) {}
}
// Тоже не лучший — много бутстрэпного кода
class UserCreationRequest2 {
public function __construct(
protected string $Username,
protected string $Password
) {}
public function getUsername() : string {
return $this->Username;
}
public function getPassword() : string {
return $this->Password;
}
}С readonly это становится лаконично и безопасно:
class UserCreationRequest {
public function __construct(
public readonly string $Username,
public readonly string $Password
) {}
}Свойства доступны извне, но не могут быть изменены после создания объекта. В сочетании с promoted constructor properties это сокращает шаблонный код и делает намерения разработчика явными.
Когда не стоит использовать readonly
- Когда объект по дизайну предполагает изменение состояния после создания.
- Для свойств, которые логично инициализировать позже внешним кодом (хотя это можно пересмотреть с точки зрения архитектуры).
- Если код активно использует рефлексию или сериализацию/десериализацию, которые полагаются на позднюю инициализацию свойств. В таких сценариях требуется дополнительное внимание.
Практические советы и методики
Ниже собраны рекомендации и практические приёмы для внедрения readonly в реальном проекте.
Факто-бокс: ключевые числа и версии
- Добавлено в PHP 8.1.
- Продукционная версия PHP 8.1 вышла в ноябре 2021 года.
- readonly применим только к типизированным свойствам и не допускает значения по умолчанию.
Мини-методология для внедрения в проект
- Идентифицируйте структурные классы: DTO, объекты запроса, конфигурации, результатные структуры.
- Для каждого — определите, должно ли поле быть изменяемым после создания.
- Если нет — пометьте как readonly и добавьте типизацию.
- Запустите тесты и статический анализатор, исправьте места, где код пытался изменять значение.
- Для сериализации/десериализации добавьте фабрики или специализированные методы или используйте конструкцию с fromArray/with… чтобы создать новые объекты, а не менять старые.
Чеклист для ревью кода
- Свойство имеет тип?
- Должно ли оно быть неизменным после конструктора?
- Можно ли пометить readonly без изменения логики?
- Нет ли места, где внешний код присваивает свойство напрямую?
- Как сериализация/десериализация обрабатывают это свойство?
Role-based checklist
- Разработчик: проверьте целостность API и обновите тесты.
- Ревьювер: убедитесь, что модификация readonly отсутствует в кодовой базе.
- Архитектор: оцените влияние на сериализацию/ORM и миграцию данных.
Примеры и сниппеты — шпаргалка
Cheat sheet: короткие примеры и варианты использования
// Объявление
public readonly int $id;
// Инициализация в конструкторе
public function __construct(int $id) {
$this->id = $id; // допустимо
}
// Попытка изменить даст Error
$this->id = 5; // ошибка
// Promotion
public function __construct(public readonly string $name) {}
// При клонировании свойство уже инициализировано
$copy = clone $obj;
$copy->name = 'x'; // ошибкаАльтернативные подходы
- Value objects: вместо mutable-сущностей используйте неизменяемые value objects и создавайте новые экземпляры при изменении.
- Builder pattern: если объект длинный и нужно отложенное заполнение, используйте билдер, который вернёт готовый immutable-объект.
- Readonly по контракту: для некоторых библиотек полезно держать обычные свойства, но документировать их как «ненужные для изменения» и полагаться на код-ревью и тесты.
Контрпримеры и сценарии, где readonly бессилен
- Когда требуется ленивое заполнение свойств внешним кодом. В этом случае readonly затруднит работу и потребует рефакторинга в пользу фабрик или билдеров.
- Когда сторонние библиотеки или ORM ожидают установить свойства после создания через рефлексию. Нужно либо адаптировать библиотеку, либо использовать DTO между слоями.
Совместимость, миграция и советы по переходу
Стратегия миграции небольшого проекта
- В тестовой ветке помечайте наиболее очевидные DTO и структуры как readonly.
- Запустите тесты, найдите места записи в эти поля.
- Рефакторьте: заменяйте внешние присвоения на фабрики или расширьте конструкторы.
- После зелёного тестового прогона — мерджите в основную ветку.
Советы для крупных кодовых баз
- Инструментально ищите прямые присвоения свойств, используйте статический анализатор.
- Делайте изменения по модулям, а не массово.
- Для сторонних библиотек добавьте адаптеры или промежуточные DTO.
Совместимость с сериализацией и ORM
Некоторые ORM/библиотеки сериализации используют рефлексию для установки значений свойств после создания объекта. Readonly усложняет это. Возможные решения:
- Использовать фабрики/репозитории, которые создают объекты целиком в конструкторе.
- Перенастроить сериализаторы на использование конструкторов или методов с аргументами.
- В крайнем случае оставить определённые свойства без readonly и документировать это решение.
Паттерны и эвристики
Модель мышления: помните три уровня mutable/immutable
- Полностью изменяемые объекты: entity, состояние приложения.
- Частично неизменяемые: основной набор полей immutable, служебные поля mutable.
- Полностью immutable objects / value objects.
Эвристика для выбора readonly: если поле моделирует идентичность, конфигурацию или данные ответа, чаще всего оно должно быть readonly.
Часто встречаемые вопросы
Q: Можно ли задать readonly для статического свойства?
A: Нет, readonly применим только к нестатическим свойствам экземпляра.
Q: Можно ли инициализировать readonly через setter внутри класса?
A: Можно присвоить значение единожды в любой области, где свойство определено, обычно это конструктор или метод класса. Но после первой инициализации запись запрещена.
Q: Как повлияют readonly на производительность?
A: Модификатор добавляет проверку времени выполнения, но в большинстве приложений это не будет узким местом. Не стоит оптимизировать преждевременно.
Примеры реальных шаблонов использования
- DTO в API: объекты запроса и ответа, передаваемые между слоями.
- Конфигурационные объекты, загружаемые при старте приложения.
- Value objects: деньги, координаты, идентификаторы.
Критерии приёмки
- Все изменяемые по дизайну поля остаются изменяемыми.
- Для структурных классов добавлены readonly для полей, которые не меняются после создания.
- Тесты покрывают попытки изменения readonly-атрибутов и ожидают ошибку.
- CI прогон без регрессий.
Заключение
Readonly-модификатор приносит в PHP встроенную поддержку иммутабельности для полей класса. Это повышает выразительность кода, снижает шаблонный код и предотвращает непреднамеренные изменения состояния. Внедрение стоит делать осознанно: начать с DTO и структур данных, прогонять тесты и исправлять места, где код полагается на позднюю запись в поля.
Readonly уже реализованы в сборках PHP 8.1 на момент разработки, а стабильная версия с этой возможностью вышла в ноябре 2021 года.
Короткое объявление для команд (100–200 слов)
Readonly-свойства в PHP 8.1 позволяют один раз задать значение поля объекта и затем запретить его изменение. Это идеально подходит для DTO, конфигураций и value objects: код становится чище, меньше геттеров, меньше шаблонного кода. При миграции начинайте с модулей, где объекты по своей природе неизменяемы, используйте фабрики и билдеры для сценариев с отложенной инициализацией, и прогоняйте тесты в CI. Учтите ограничения: readonly работает только с типизированными свойствами и запрещает дефолтные значения в объявлении. Также будьте осторожны с ORM и сериализаторами, которые могут требовать адаптации.
Однострочный глоссарий
readonly — модификатор свойства, позволяющий записать значение один раз в области определения, после чего любые записи вызывают ошибку.
Социальный превью
OG title: Readonly-свойства в PHP 8.1: быстрое руководство OG description: Как использовать readonly для безопасных DTO и immutable-полей, примеры, ограничения и советы по миграции.
Важное
- Readonly повышают безопасность и читаемость.
- Проверяйте взаимодействие с ORM и сериализаторами.
- Делайте миграцию поэтапно и покрывайте тестами.
Похожие материалы
Измерить рост на iPhone с LiDAR
PUBG падает в Windows 11 — как исправить
Исправить ошибку «Oops! Something went wrong» в YouTube
Экран входа macOS — настройки и советы
Удалить историю Google Bard и отключить её