<? phpukraine СПІВБЕСІДИ
Пошук по платформі
CORE PHP · JUNIOR ЧАСТО ПИТАЮТЬ

Що таке readonly-властивості й readonly-класи?

readonly дозволяє записати типізовану властивість рівно один раз зі скоупу класу; з PHP 8.2 модифікатор можна поставити на весь клас.

Як зробити обʼєкт незмінним без приватних властивостей і геттерів?
Чому не можна змінити властивість, хоча вона public?
Чим readonly відрізняється від const і від private з геттером?
Що робити, якщо потрібна копія обʼєкта з іншим значенням readonly-поля?
readonly immutability PHP 8.1 PHP 8.2 value object

Модифікатор readonly зʼявився в PHP 8.1 і застосовується до типізованої властивості екземпляра. Правило одне: у таку властивість можна записати значення рівно один раз і лише з коду класу, який її оголосив. Тому найчастіша форма — просування властивостей у конструкторі: public function __construct(public readonly int $amount) {}. Значення за замовчуванням заборонене (public readonly int $x = 5; не скомпілюється), тип обовʼязковий, static readonly не існує. Спроба присвоїти вдруге або ззовні дає Error: Cannot modify readonly property Money::$amount, а unset()Error: Cannot unset readonly property.

Важливо не плутати readonly з const і з «незмінним обʼєктом». const — константа класу, спільна для всіх екземплярів і відома на етапі компіляції; readonly — значення конкретного обʼєкта, обчислене в рантаймі під час створення. І захист тут поверхневий: заборонено перезаписати саму властивість, а не те, що всередині. Масив у readonly-властивості змінити не можна ($this->items[] = $x — це модифікація властивості), а от у вкладеного обʼєкта цілком можна викликати мутуючий метод. Глибока незмінність досягається лише тим, що всередину кладуть скаляри або теж незмінні обʼєкти.

PHP 8.2 додав readonly class: усі властивості екземпляра стають readonly автоматично, нетипізовані й статичні властивості заборонені, динамічні властивості теж (#[AllowDynamicProperties] на такий клас поставити не можна), а нащадок readonly-класу зобовʼязаний бути readonly. Це майже готове визначення value object: readonly class OrderId { public function __construct(public string $value) {} }.

Найболючіше місце readonly — зміна значення в копії. Сам clone працював завжди, але в PHP 8.1–8.2 навіть __clone() не міг перезаписати readonly-властивість, тому глибоке копіювання доводилося емулювати конструктором. PHP 8.3 це виправив: під час __clone() readonly-властивості вважаються неініціалізованими й приймають один новий запис. PHP 8.5 пішов далі й дав синтаксис clone $obj with { amount: 50 }: нові значення застосовуються одразу після копіювання і до виклику __clone(), а перевірки видимості й readonly виконуються за скоупом, у якому стоїть вираз — тобто readonly-поле так само можна змінити лише зсередини класу. Ручні withX()-методи від цього не зникають, але їхнє тіло стискається до одного рядка.

Практичний критерій вибору такий. readonly замінює приватний сеттер (і зв’язку «private-властивість плюс геттер») там, де значення задається під час створення й більше ніколи не змінюється: ідентифікатори, гроші, DTO вхідного запиту, налаштування сервісу. Якщо ж значення має оновлюватися пізніше або обчислюватися ліниво, readonly лише заважає — жодного кешу чи memoize всередині такого обʼєкта не зробити. Для випадку «читають усі, пише лише клас, але не один раз» у PHP 8.4 є асиметрична видимість public private(set) int $x, а для обчислюваних значень — property hooks.

final class Money
{
    public function __construct(
        public readonly int $amount,        // типізована, без default, пишеться раз
        public readonly string $currency,
    ) {}

    public function withAmount(int $amount): self
    {
        // PHP 8.5: копія зі зміненим полем; readonly перезаписується
        // лише зі скоупу класу, який його оголосив
        return clone $this with { amount: $amount };
        // до 8.5: return new self($amount, $this->currency);
    }
}

$price = new Money(100, 'UAH');
echo $price->amount;          // читати можна звідусіль
// $price->amount = 200;      // Error: Cannot modify readonly property
// unset($price->amount);     // Error: Cannot unset readonly property

// PHP 8.2: усі властивості класу readonly автоматично
readonly class OrderId
{
    public function __construct(public string $value) {}
}

final class Order
{
    public function __construct(public readonly Money $total) {}

    public function __clone(): void
    {
        // PHP 8.1–8.2: Error; з 8.3 readonly можна перезаписати в __clone
        $this->total = clone $this->total;
    }
}
Що readonly зʼявився в PHP 8.1 для властивостей, а readonly class — у PHP 8.2, і що readonly-клас робить readonly всі свої властивості.
Що властивість має бути типізованою, без значення за замовчуванням, не static, і що записати її можна лише зі скоупу класу, який її оголосив, — навіть якщо вона public.
Що readonly не означає глибокої незмінності: обʼєкт усередині readonly-властивості можна мутувати, забороняється лише перезапис самого посилання.
Що зміна значення дає Error: Cannot modify readonly property, а не warning, і що unset() readonly-властивості теж кидає Error.
Практику клонування: у 8.1–8.2 __clone не може перезаписати readonly, з 8.3 може, а з 8.5 є `clone ... with { ... }` замість ручних withX()-конструкторів.
Писати `public readonly int $x = 5;` — readonly-властивість не може мати значення за замовчуванням, це помилка компіляції.
Оголошувати `public readonly $x;` без типу — «Readonly property must have type».
Присвоювати readonly-властивість ззовні класу (`$obj->x = 1;`) і чекати, що public це дозволить: ініціалізація можлива лише зі скоупу оголошення.
Вважати readonly аналогом const: const — це константа класу, обчислена на етапі компіляції й спільна для всіх, readonly — значення конкретного екземпляра, задане в рантаймі.
Ставити readonly на властивість, яку треба лениво заповнити або перерахувати пізніше: кеш чи memoize всередині обʼєкта після цього неможливий.
ПОРАДА

Скажіть, що readonly — це не «незмінний обʼєкт», а «властивість, у яку можна записати один раз зі скоупу класу», і одразу назвіть шлях зміни: новий екземпляр, а з PHP 8.5 — `clone $this with { ... }`.

оновлено 3 вересня 2026 · ліцензія CC-BY-SA-4.0 Знайшли неточність? Напишіть →
ПЕРЕВІРТЕ СЕБЕ

readonly (PHP 8.1) вимагає типу, забороняє значення за замовчуванням і дозволяє рівно один запис зі скоупу оголошення; вкладені обʼєкти при цьому лишаються мутабельними.