Ассиметричная видимость свойств в PHP 8.4

В PHP 8.4 появилась новая возможность — асимметричная видимость свойств. Она позволяет задавать разные уровни доступа на чтение и запись. В статье — обзор синтаксиса, примеры из реального кода и рекомендации по применению.

Ассиметричная видимость свойств в PHP 8.4

Всем привет! Меня зовут Никита, и я Backend-разработчик в YCLIENTS. Как и многие современные компании, мы стремимся идти в ногу со временем, поэтому активно внедряем актуальные и стабильные технологии. Например, не так давно мы перешли на PHP 8.4 — и, надо сказать, этот релиз принёс немало интересных нововведений.

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

Давайте разберёмся, как это работает и где может пригодиться!

Что такое ассиметричная видимость?

В PHP 8.4 вы можете задать разные уровни видимости для геттера и сеттера свойства. Например, свойство может быть доступно для чтения публично, но доступно для записи только внутри класса или его подклассов. Это позволяет скрыть детали реализации, предоставляя при этом доступ к данным.

Кстати, если вы не хотите, чтобы значение свойства можно было изменить после установки, возможно, вам лучше использовать свойства с модификатором readonly.

Асимметричная видимость свойства задаётся с использованием следующего синтаксиса:

Синтаксис

[GETTER_VISIBILITY] [SETTER_VISIBILITY(set)] [TYPE] $propertyName;

Где:

  • GETTER_VISIBILITY — может быть publicprotected или private.
  • SETTER_VISIBILITY(set) — может быть publicprotected или private.
  • TYPE — тип свойства (например, stringintarray и т.д.).

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

protected private(set) string $propertyName;

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

Как использовать асимметричную видимость в PHP

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

Мы создадим простой класс Appointment (мы же всё-таки про онлайн-запись 😀) с тремя свойствами: clientName (имя клиента), phone (номер телефона клиента) и comment (комментарий к записи). Каждое свойство будет иметь разные уровни видимости для геттера и сеттера.

Класс Appointment будет выглядеть следующим образом:

Пример

class Appointment
{
    public function __construct(
        public protected(set) string $clientName,
        public private(set) string $phone,
        protected private(set) string $comment,
    ) {}
}

Теперь давайте возьмём наш класс Appointment и посмотрим, как можно читать и записывать свойства с асимметричной видимостью.

Чтение свойств с асимметричной видимостью

Начнём с того, как можно читать свойства, у которых заданы разные уровни доступа для чтения и записи. Для этого мы создадим экземпляр класса Appointment и попытаемся получить доступ к его свойствам:

Чтение свойств с ассиметричной видимостью

$appointment = new Appointment(
    clientName: 'Никита',
    phone: '+79001234567',
    comment: 'Опоздает на 5 минут',
);

echo $appointment->clientName; // ✅ Доступно публично
echo $appointment->phone; // ✅ Доступно публично
echo $appointment->comment; // ❌ Ошибка: свойство protected

Как видно из примера, мы можем получить доступ к свойствам clientName и phone публично. Однако мы не можем получить доступ к свойству comment извне класса, потому что оно имеет защищённую (protected) видимость. Это означает, что читать его можно только внутри самого класса или в его подклассах.

Попытка обращения к свойству comment снаружи приведёт к ошибке со следующим сообщением:

Fatal error: Uncaught Error: Cannot access protected property Appointment::$comment in /in/qskLk:20

Чтобы получить доступ к свойству comment, нам нужно создать метод внутри класса Appointment (или в классе, который наследует Appointment), который будет возвращать значение этого свойства. Например:

class Appointment
{
    public function __construct(
        public protected(set) string $clientName,
        public private(set) string $phone,
        protected private(set) string $comment,
    ) {}

    public function getComment(): string
    {
        return $this->comment;
    }
}

Теперь мы сможем получить содержимое статьи, вызвав метод getComment:

$appointment = new Appointment(
    clientName: 'Никита',
    phone: '+79001234567',
    comment: 'Опоздает на 5 минут',
);

echo $appointment->getComment(); // Комментарий к записи

Запись в свойства с асимметричной видимостью

На примере нашего класса Appointment, давайте посмотрим, как можно записывать значения в свойства с асимметричной видимостью.

Мы определили свойство comment как protected private(set) string $comment, что означает, что у него приватный сеттер. Это значит, что если мы попытаемся напрямую изменить значение свойства comment из вне класса, возникнет ошибка. Например:

$appointment = new Appointment(
    clientName: 'Никита',
    phone: '+79001234567',
    comment: 'Опоздает на 5 минут',
);

// ❌ Мы не можем публично изменить значение свойства comment, так как у него protected видимость
$appointment->comment = 'Успевает во время';

Попытка выполнить приведённый выше код вызовет ошибку со следующим сообщением:

Fatal error: Uncaught Error: Cannot access protected property Appointment::$comment in /in/ZQuG4:19

Вместо этого, в таком случае, нам стоит создать метод-сеттер внутри класса Appointment, который позволит устанавливать значение свойства comment. Например:

class Appointment
{
    public function __construct(
        public protected(set) string $clientName,
        public private(set) string $phone,
        protected private(set) string $comment,
    ) {}

    public function setComment(string $comment): void
    {
        $this->comment = $comment;
    }
}

После этого мы сможем использовать этот метод следующим образом:

$appointment = new Appointment(
    clientName: 'Никита',
    phone: '+79001234567',
    comment: 'Опоздает на 5 минут',
);

$appointment->setComment('Ну точно успевает во время');

Пропуск публичного геттера

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

Например, рассмотрим следующий класс:

class Appointment
{
    public function __construct(
        public protected(set) string $clientName,
        public private(set) string $phone,
        protected private(set) string $comment,
    ) {}
}

Мы можем переписать определения свойств следующим образом:

class Appointment
{
    public function __construct(
        protected(set) string $clientName,
        private(set) string $phone,
        protected private(set) string $comment,
    ) {}
}

В приведённом выше примере мы убрали явное указание публичной видимости геттера у свойств clientName и phone, поскольку они и так читаются публично. Однако для свойства comment пришлось оставить protected видимость геттера.

Особенности асимметричной видимости в PHP

При использовании асимметричной видимости в PHP стоит учитывать несколько важных моментов:

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

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

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

class Appointment
{
    public function __construct(
        protected(set) string $clientName,
        private(set) $phone,
        protected private(set) string $comment,
    ) {}
}

Попытка выполнить приведённый выше код вызовет ошибку со следующим сообщением:

Fatal error: Property with asymmetric visibility Appointment::$phone must have type in /in/YHqBi on line 4

set Видимость должна быть более ограниченной чем get Видимость

Видимость сеттера должна быть такой же или более ограниченной, чем видимость геттера. Например, нельзя объявить свойство с видимостью protected public(set), так как это означало бы, что сеттер (public) доступен шире, чем геттер (protected), что недопустимо.

Рассмотрим пример класса, в котором свойство phone объявлено с неправильной асимметричной видимостью:

class Appointment
{
    public function __construct(
        protected(set) string $clientName,
        private protected(set) string $phone,
        protected private(set) string $comment,
    ) {}
}

Попытка выполнить приведённый выше код вызовет ошибку со следующим сообщением:

Fatal error: Visibility of property Appointment::$phone must not be weaker than set visibility in /in/3LFGh on line 4

Свойства с private(set) являются final

Если свойство объявлено с приватным сеттером (private(set)), оно автоматически считается final. Это значит, что такое свойство нельзя переопределить в классе-наследнике.

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

class Appointment
{
    public function __construct(
        public string $clientName,
        private(set) string $phone,
        protected private(set) string $comment,
    ) {}
}

class SubAppointment extends Appointment
{
    private string $phone;

    // ...
}

$appointment = new SubAppointment(
    clientName: 'Никита',
    phone: '+79001234567',
    comment: 'Записывает сына',
);

В приведённом выше примере мы попытались переопределить свойство phone в классе SubAppointment. Однако, поскольку свойство phone имеет видимость private(set), оно считается final, и при попытке запуска такого кода будет выброшена ошибка:

Fatal error: Cannot override final property Appointment::$phone in /in/FJ8DF on line 11

Заключение

Асимметричная видимость в PHP 8.4 — это небольшой, но очень полезный инструмент в арсенале разработчика. Он позволяет делать код более гибким и безопасным, чётко разделяя доступ к свойствам на чтение и запись. Пока что фича не получила массового распространения, но, уверен, со временем её начнут применять чаще — особенно в проектах, где важна инкапсуляция и контроль доступа.

Если вы ещё не пробовали асимметричную видимость в деле — самое время поэкспериментировать! Возможно, именно она поможет вам сделать код чище и удобнее.

Подписаться на новые выпуски блога

Не пропустите последние обновления.
i.ivanov@yandex.ru
Подписаться