В PHP 8.1 появилась поддержка свойств классов «только для чтения» (read-only). Свойство класса, объявленное как read-only, может быть инициализировано только один раз, и последующие изменения этого свойства не допускаются.

Свойства класса только для чтения объявляются с помощью ключевого слова  readonly * в типизированном свойстве.

class User {
    public readonly int $uid;

    public function __construct(int $uid) {
        $this->uid = $uid;
    }
}

$user = new User(42); 

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

class User {
    public readonly int $uid;

    public function __construct(int $uid) {
        $this->uid = $uid;
    }
} $user = new User(42);
$user->uid = 56; Error: Cannot modify readonly property User::$uid in ...:... 

 readonly  в качестве имени функции 

Начиная с PHP 8.1 и более поздних версий,  readonly  является зарезервированным ключевым словом PHP. Тем не менее, оно явно разрешает объявлять функции с именем  readonly  для совместимости с существующими PHP-приложениями, которые объявляют собственные функции  readonly , такими как WordPress.

Свойства  readonly  в конструкторе 

Продвижение свойств в конструкторе (Constructor property promotion), появившееся в PHP 8.0, также поддерживает новый флаг  readonly  в конструкторе.

class User {
    public function __construct(public readonly int $uid) {}
} 

Объявление выше эквивалентно следующему:

class User {
    public readonly int $uid;

    public function __construct(int $uid) {
        $this->uid = $uid;
    }
} 

Инициализация свойств 

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

class User {
    public readonly int $uid;

    public function fetch(int $uid) {
        $this->uid = $uid;
    }
}

$user = new User();
$user->fetch(42); 

Приведенный выше фрагмент кода корректен, поскольку свойство  readonly   $uid  записывается только один раз, из метода  fetch() . Однако второй вызов функции  fetch()  приведет к выбросу Error, так как он предполагает перезапись свойства  $uid .

$user = new User();
$user->fetch(42);
$user->fetch(16); Error: Cannot modify readonly property User::$uid in ...:... 

Инициализация свойства  readonly   вне области видимости класса не допускается:

class User {
    public readonly int $uid;
}
$user = new User();
$user->uid = 9; Error: Cannot initialize readonly property User::$uid from global scope in ...:... 

Контроль ограничения «только для чтения» 

PHP активно препятствует изменению свойства  readonly . Это включает в себя прямую установку значения, инкремент, ссылки и операции с массивами.

class User {
    public readonly int $uid;

    public function __construct(int $uid) {
        $this->uid = $uid;
    }
}

$user = new User(42); 

Ни одно из изменений в следующем неисчерпывающем списке не допускается:

$user->uid = 16; $user->uid++; $uid_ref =& $user->uid; 

Удаление свойства  readonly  через unset 

Кроме того, удаление уже инициализированного свойства  readonly  с помощью unset также не допускается:

unset($user->uid); Error: Cannot unset readonly property User::$uid in ...:... 

Ограничения 

Свойства  readonly  могут значительно сократить шаблонный код, который часто используется для ограничения доступа на запись к свойствам класса, что теперь можно упростить с помощью свойства  public readonly . Однако флаг  readonly  может не подойти для некоторых сценариев использования.

Поддерживается только для типизированных свойств 

Свойства только для чтения поддерживаются только для типизированных свойств. Это связано с тем, что свойства, объявленные без указания типа, неявно имеют значение  null  до тех пор, пока не будет задано значение, что несовместимо с ограничением read-only. Типизированные свойства остаются «неинициализированными» до тех пор, пока им не будет присвоено значение.

Попытка объявить свойство  readonly  без указания типа приведет к ошибке:

class User {
    public readonly $uid;
} Fatal error: Readonly property User::$uid must have type in ... on line ... 

Мутабельность 

Свойство  readonly  не обеспечивает неизменяемость (immutability) объектов. Например, если свойство  readonly  содержит объект, сам этот объект может изменяться:

class User {
    public string $username;
}

class Post {
    public readonly User $author;
    public function __construct(User $author) {
        $this->author = $author;
    }
}

$user = new User();
$user->username = 'Foo';

$post = new Post($user);
$user->username = 'Bar'; 

В приведенном выше фрагменте класс  Post  хранит свойство  readonly   author . Сам объект  $user  может изменяться, и свойство  readonly  не предотвращает модификацию самого объекта.

Если требуется неизменяемость, обязательно клонируйте ( clone ) объект перед присваиванием:

class Post {
    public readonly User $author;
    public function __construct(User $author) {
-       $this->author = $author;
+       $this->author = clone $author;
    }
} 

Влияние на обратную совместимость

 readonly  является зарезервированным ключевым словом в PHP 8.1. Использование имени  readonly  (без учета регистра) для любых существующих классов или других символов приведет к синтаксической ошибке в PHP 8.1.

Перенести функциональность  readonly  в более старые версии PHP невозможно. Тем не менее, аннотации  @readonly  /  @property-read  распознаются инструментами статического анализа, такими как Psalm.

class User {
    /** @readonly */
    public string $username;
} 

RFC Обсуждение Реализация