PHP 8.2: Классы только для чтения (Readonly Classes)

PHP 8.2: Классы только для чтения (Readonly Classes)

PHP 8.2 добавляет новый способ объявления классов: вы можете сделать их доступными только для чтения

На практике это означает, что все свойства этого класса будут доступны только для чтения. Это особенно полезно при использовании DTO или объектов-значений (Value Object), когда класс имеет только общедоступные свойства для чтения .

Другими словами, вместо того, чтобы писать это:


class ItemDTO
{
public function __construct(
public readonly string $title,
public readonly Status $status,
public readonly ?DateTimeImmutable $published_at = null,
) {}
}

Теперь вы можете написать это:

 

readonly class ItemDTO
{
public function __construct(
public string $title,
public Status $status,
public ?DateTimeImmutable $published_at = null,
) {}
}

Я уже ранее писал о свойствах только для чтения в PHP 8.1, поэтому давайте сначала быстро вспомним их:


  • свойства readonly могут быть записаны только один раз - обычно в конструкторе;
  • только типизированные свойства могут быть сделаны readonly;
  • они не могут иметь значения по умолчанию (если только вы не используете объявление свойства в конструкторе);
  • флаг readonly нельзя изменить при наследовании;
  • вы не можете отменить свойства только для чтения.

Также readonly могут быть объявлены абстрактные и финальные классы. Порядок ключевых слов не имеет значения:


abstract readonly class ItemDTO { /* ...  */ }
final readonly class ItemDTO { /* ... */ }

Можно объявить readonly класс без каких-либо свойств,  позволяя дочерним классам явно объявлять свои readonly свойства. Как и в случае со всеми ключевыми словами PHP, ключевое слово readonly нечувствительно к регистру.   

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


Перечисления, Трейты, Интерфейсы как только для чтения 

Enum вообще не могут содержать свойства, и их нельзя объявлять как классы только для чтения. Traits не могут быть объявлены readonly. Интерфейсы не могут быть объявлены readonly.

Попытка объявить enum, трейты или интерфейсы как  readonly приводит к ошибке Parse:


readonly interface SomethingClass {}

Parse error: syntax error, unexpected token "interface", expecting "abstract" or "final" or "readonly" or "class" in ... on line ...


Написать один раз

Все свойства класса только для чтения могут быть записаны только один раз и не могут быть отменены:


readonly class ItemDTO { /* … */ }

$itemDTO = new ItemDTO(/* … */);

$itemDTO->title = 'что-то другое'; //ошибка

unset($itemDTO->title); //ошибка

 

Только типизированные свойства

Класс только для чтения может иметь только типизированные свойства, для свойств, которые не могут быть строго типизированы, рассмотрите возможность использования типа mixed:


readonly class ItemDTO
{
public string $title;

public $mixed; //ошибка Fatal error: Readonly property Test::$test must have type in ... on line ...

public mixed $mixed; //правильно
}

  

Нет статических свойств

Поскольку свойства только для чтения не могут быть статическими, классы только для чтения тоже не могут иметь никаких статических свойств:


readonly class ItemDTO
{
public static string $title; //ошибка
}

 

Нет значений по умолчанию

Свойства класса только для чтения не могут иметь значения по умолчанию, только если вы не используете объявление свойства в конструкторе:


readonly class ItemDTO
{
public string $title = 'default'; //ошибка
}

А вот так вот будет нормально:


readonly class ItemDTO
{
public function __construct(
public string $title = 'default',
) {}
}

 

Никаких изменений при наследовании 

Вы не можете изменить readonly флаг класса во время наследования:


readonly class ItemDTO { /* … */ }

class ItemDTO extends ItemDTO { /* … */ } //ошибка Fatal error: Non-readonly class ItemDTO cannot extend readonly class ItemDTO in ... on line ...


Нет динамических свойств

Классы только для чтения также не допускают динамических свойств. Это не окажет большого влияния, поскольку динамические свойства в любом случае устарели в PHP 8.2, но так же это означает, что вы не можете добавить атрибут #[AllowDynamicProperties] в классы только для чтения: 


#[AllowDynamicProperties] //ошибка Fatal error: Cannot apply #[AllowDynamicProperties] to readonly class ItemDTO in ... on line ...
readonly class ItemDTO { /* … */ }


Рефлексия 

Появились новые методы Reflection, чтобы определить, является ли класс доступным только для чтения или назначит флаг readonly самому:

  • ReflectionClass::isReadOnly()
  • ReflectionClass::getModifiers() - битовая маска, которая показывает наличие readonly флага
  • ReflectionClass::IS_READONLY - назначить флаг readonly.


Отказ от статуса только для чтения

После того как класс объявлен readonly, отказаться от этого невозможно. Это относится и к подклассам, потому что подклассы также должны быть явно объявлены readonly.


Изменчивость

Обратите внимание, что readonly классы не обеспечивают полной иммутабельности (неизменности). Хотя readonly классы идеально подходят для объектов-значений, которые гарантируют, что их данные не изменятся, объекты, хранящиеся в readonly свойстве, могут измениться. Это то же самое поведение, что и у readonly свойств.


Влияние обратной совместимости

Синтаксис readonly класса является новым в PHP 8.2, и объявленные классы readonly приводят к ошибке Parse в более старых версиях PHP.

В качестве временной меры для readonly классов рассмотрите возможность объявления всех свойств как readonly, что поддерживается, начиная с PHP 8.1.

Сергей Мухин

Веб-разработчик со стажем программирования более 12 лет, постоянно учусь, люблю делать новые проекты.

Есть вопросы?

Я почти всегда в режиме онлайн

Связаться со мной