Readonly чи private(set): яку властивість краще обрати в PHP

Перекладено ШІ 0 stitcher.io 30 липня, 2026

Асиметрична видимість у PHP 8.4 змушує розробників переглянути доцільність використання readonly властивостей. Розбираємося, що краще обрати для архітектури ваших Data Objects і чи не настав час мові нарешті отримати повноцінні structs.

PHP — це суцільний хаос. Давайте визнаємо це одразу.

Я люблю цю мову, але деякі її аспекти відверто дратують. Яскравий приклад — необхідність обирати між властивостями readonly та властивостями з приватним доступом на запис. Поясню, у чому проблема.

Властивості readonly з'явилися в PHP 8.1. Вони дозволяють встановити значення лише один раз, після чого змінити його неможливо:

final class Book
{
    public function __construct(
        public readonly string $title,
    ) {}
}
$book = new Book('Timeline Taxi');

$book->title = 'Timeline Taxi 2';
Cannot modify readonly property Book::$title

До слова, PHP дозволяє створювати об'єкт без миттєвого присвоєння значення readonly властивості. Валідність перевіряється лише під час читання — це частина логіки типізованих властивостей, закладеної ще в PHP 7.4:

final class Book
{
    public readonly string $title;
}

$book = new Book();
echo $book->title;
Typed property Book::$title must not be accessed before initialization

// Присвоєння значення після конструктора працює коректно:
$book->title = 'Timeline Taxi';

Потім у PHP 8.4 з'явилася «асиметрична видимість». Вона дозволяє встановлювати різні рівні доступу (public, protected або private) залежно від операції: читання (get) чи запису (set).

Наприклад, ви можете визначити властивість як private(set):

final class Book
{
    public function __construct(
        private(set) string $title,
    ) {}
}

private(set) означає, що властивість доступна для читання ззовні, але змінювати її можна лише всередині класу. Це скорочення від public private(set). Тобто ви можете змінити заголовок книги через метод класу:

final class Book
{
    public function __construct(
        private(set) string $title,
    ) {}
    
    public function markDraft(): self
    {
        // Зміна всередині класу дозволена
        $this->title .= ' (Draft)';
    
        return $this;
    }
}

Але спроба змінити її ззовні призведе до помилки:

$book = new Book('Timeline Taxi');

$book->title .= ' (Draft)';
Cannot modify readonly property Book::$title

Чому це важливо? Здавалося б, це різні інструменти: один запобігає будь-яким змінам після ініціалізації, інший — обмежує коло тих, хто може ці зміни вносити.

Коли з'явився readonly (за три роки до асиметричної видимості), його масово почали використовувати для створення data objects — структур, що передають типізовані дані в системі. Це надзвичайно зручний патерн, про який я писав ще у 2018 році.

Завдяки readonly ми отримали можливість створювати класи з публічними властивостями без геттерів та сеттерів. Оскільки дані все одно неможливо змінити, навіщо писати зайвий шаблонний код (boilerplate)?

final class Book
{
    public function __construct(
        public readonly string $title,
        public readonly Author $author,
        public readonly ChapterCollection $chapters,
        public readonly Publisher $publisher,
        public readonly null|DateTimeImmutable $publishedAt = null,
    ) {}
}

Цей підхід став настільки популярним, що в PHP 8.2 додали синтаксис для цілих readonly класів:

final readonly class Book
{
    public function __construct(
        public string $title,
        public Author $author,
        public ChapterCollection $chapters,
        public Publisher $publisher,
        public null|DateTimeImmutable $publishedAt = null,
    ) {}
}

Але тепер ми маємо PHP 8.4 з асиметричною видимістю. І хоча технічно це інша функція, вона дозволяє досягти того самого результату — захисту об'єкта від зовнішніх втручань — просто позначивши властивості як private(set):

final class Book
{
    public function __construct(
        private(set) string $title,
        private(set) Author $author,
        private(set) ChapterCollection $chapters,
        private(set) Publisher $publisher,
        private(set) null|DateTimeImmutable $publishedAt = null,
    ) {}
}

Можна стверджувати, що асиметрична видимість краща за readonly, бо вона гнучкіша — ви все ще можете змінювати дані всередині класу.

Крім того, іноді виникає потреба змінити readonly об'єкт шляхом копіювання даних у новий екземпляр. На жаль, у PHP досі немає повноцінного виразу clone with для перезапису таких властивостей під час клонування. Навіть заплановані зміни в clone для PHP 8.5 не вирішують цю проблему повністю. Це досить глибока тема, про яку я зняв окреме відео:

Заперечувати марно: для об'єктів даних readonly програє асиметричній видимості. Проблема в тому, що readonly вже три роки як стандарт і використовується всюди. Я помічаю, як у новому коді (навіть у моєму власному фреймворку Tempest) з'являється асиметрична видимість, тоді як старі частини залишаються на readonly. Це створює плутанину. Між readonly та private(set) є семантичні відмінності, особливо в питанні клонування, але для типових задач вони виглядають взаємозамінними.

То що тепер? Замінити readonly на private(set) всюди? Чи варто депрекувати readonly у майбутньому? Чи, можливо, ігнорувати нову фічу заради звички? Як новачкам зрозуміти, що саме обрати?

Найгірше те, що ми це передбачали. Коли асиметрична видимість була лише ідеєю, я писав, як ці дві функції конфліктуватимуть між собою та плутатимуть розробників роками. Мені більше до вподоби асиметрична видимість, але її поява після readonly створює безлад.

Іронія в тому, що всі ці нововведення — readonly властивості та класи, асиметрична видимість, constructor property promotion — це лише милиці. Нам просто потрібні нормальні struct для типізованого представлення даних.

struct Book
{
    string $title;
    Author $author;
    ChapterCollection $chapters;
    Publisher $publisher;
    null|DateTimeImmutable $publishedAt = null;
}

Я впевнений, що з радістю обміняв би всі ці складні фічі на прості та зрозумілі struct.

Так, PHP — це хаос. Прекрасний, улюблений хаос, який я ні на що не проміняю.

Але все ж таки, який же це хаос.

Популярні

Інше, що варто прочитати

60 Оновлено 26 червня, 2026

Усе, що нам відомо про Livewire 4

Нова версія Livewire 4, представленої Келебом Порзіо на Laracon US 2025, обіцяє значні покращення у швидкості та організації компонентів. Які з інноваційних функцій підкорять ваше серце? Читайте далі, щоб дізнатися більше про те, як Livewire 4 полегшить вашу роботу

38 Оновлено 26 червня, 2026

4 поширені помилки Vite у Laravel

Використання Vite для створення фронтенд-ресурсів у вашому додатку Laravel може бути захоплюючим, але іноді ви можете стикнутися з певними помилками. У цій статті ми розглянемо чотири поширені помилки, з якими ви можете зіткнутися, а також підкажемо способи їх усунення, щоб ви могли знову зосередитися на розробці вашого додатку

79 Оновлено 26 червня, 2026

Laravel Boost — ваш стартовий набір для програмування з використанням штучного інтелекту

Вперше у світі Laravel з'являється можливість, яка значно спростить ваше повсякденне програмування завдяки новому пакету Laravel Boost. Читайте статтю, щоб дізнатися, як посилена інтеграція штучного інтелекту може підвищити ефективність вашої роботи та оптимізувати створення проектів у Laravel