Гид по технологиям

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

8 min read PHP Обновлено 23 Nov 2025
Readonly-свойства в PHP 8.1: руководство
Readonly-свойства в PHP 8.1: руководство

Логотип PHP

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-свойства — это синтаксическое расширение, которое можно внедрять постепенно. Нет обратной несовместимости, их использование опционально. Однако есть важные ограничения, которые нужно учитывать.

  1. Нельзя задавать значение по умолчанию в объявлении readonly-свойства
class Demo {
    protected readonly string $foo = 'bar';
}

Это бы означало, что свойство уже инициализировано на этапе определения, и его нельзя изменить позже. Поэтому такие дефолты запрещены — используйте константу вместо этого:

class Demo {
    const FOO = 'bar';
}
  1. Только типизированные свойства
readonly

применим только к типизированным свойствам. Неприменимо к не типизированным:

class Demo {
    protected readonly $foo; // недопустимо
}

Непривязанные свойства имеют неявное значение null. Типизированные свойства различают состояния «неинициализировано» и «null», поэтому readonly требует явного типа.

  1. Наследование и изменение модификатора

В дочерних классах нельзя добавлять или убирать

readonly

для свойств, унаследованных от родителей. Разрешение подобных изменений могло бы нарушить гарантию родителя о неизменности данных и привести к побочным эффектам. RFC рассматривает readonly как намеренное ограничение, и оно сохраняется при наследовании.

  1. Область видимости и инициализация

Readonly-свойства можно устанавливать только в той области, где они определены. Это означает, что публичное свойство нельзя инициализировать извне, даже если оно ещё не было установлено:

class Demo {
    public readonly string $foo;
}

$d = new Demo();
$d->foo = 'bar'; // незаконно

Инициализация должна происходить внутри класса, который определил свойство. Благодаря этому поведение ближе к неизменяемым полям в других языках.

  1. Одинаковое поведение для всех записей

Модификатор не делает различий между внутренней и внешней записью. Нельзя, например, сделать свойство публично доступным, но записываемым только внутри класса. Это поведение может быть расширено в будущих спецификациях.

  1. Клонирование и неочевидная инициализация

Клонирование сохраняет состояние инициализированных свойств. Следующий код не сработает так, как могут ожидать некоторые разработчики:

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 применим только к типизированным свойствам и не допускает значения по умолчанию.

Мини-методология для внедрения в проект

  1. Идентифицируйте структурные классы: DTO, объекты запроса, конфигурации, результатные структуры.
  2. Для каждого — определите, должно ли поле быть изменяемым после создания.
  3. Если нет — пометьте как readonly и добавьте типизацию.
  4. Запустите тесты и статический анализатор, исправьте места, где код пытался изменять значение.
  5. Для сериализации/десериализации добавьте фабрики или специализированные методы или используйте конструкцию с 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 между слоями.

Совместимость, миграция и советы по переходу

Стратегия миграции небольшого проекта

  1. В тестовой ветке помечайте наиболее очевидные DTO и структуры как readonly.
  2. Запустите тесты, найдите места записи в эти поля.
  3. Рефакторьте: заменяйте внешние присвоения на фабрики или расширьте конструкторы.
  4. После зелёного тестового прогона — мерджите в основную ветку.

Советы для крупных кодовых баз

  • Инструментально ищите прямые присвоения свойств, используйте статический анализатор.
  • Делайте изменения по модулям, а не массово.
  • Для сторонних библиотек добавьте адаптеры или промежуточные DTO.

Совместимость с сериализацией и ORM

Некоторые ORM/библиотеки сериализации используют рефлексию для установки значений свойств после создания объекта. Readonly усложняет это. Возможные решения:

  • Использовать фабрики/репозитории, которые создают объекты целиком в конструкторе.
  • Перенастроить сериализаторы на использование конструкторов или методов с аргументами.
  • В крайнем случае оставить определённые свойства без readonly и документировать это решение.

Паттерны и эвристики

Модель мышления: помните три уровня mutable/immutable

  1. Полностью изменяемые объекты: entity, состояние приложения.
  2. Частично неизменяемые: основной набор полей immutable, служебные поля mutable.
  3. Полностью 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 и сериализаторами.
  • Делайте миграцию поэтапно и покрывайте тестами.
Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Измерить рост на iPhone с LiDAR
Гид

Измерить рост на iPhone с LiDAR

PUBG падает в Windows 11 — как исправить
Гейминг

PUBG падает в Windows 11 — как исправить

Исправить ошибку «Oops! Something went wrong» в YouTube
Техподдержка

Исправить ошибку «Oops! Something went wrong» в YouTube

Экран входа macOS — настройки и советы
macOS

Экран входа macOS — настройки и советы

Удалить историю Google Bard и отключить её
Конфиденциальность

Удалить историю Google Bard и отключить её

TinyLetter для блогеров: быстро и просто
Email-маркетинг

TinyLetter для блогеров: быстро и просто