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

Источник: «Asymmetric visibility for PHP properties»
Одной из менее заметных функций, появившихся в PHP 8.4, каламбурно говоря, является ассиметричная видимость PHP свойств. Это обновление текущей видимости, а также ряд усовершенствованных возможностей по сравнению с readonly, хуками свойств или магическими методами. Это не для всех, но может оказаться весьма полезным. Давайте посмотрим!

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

Я не знал, что видимость симметрична. Как могут быть симметричны public, protected и private? На самом деле это сводится к чтению и записи. Свойство является private как для чтения, так и для записи. Вот и всё.

<?php

class Square {
public int $side;
public int $area;

function __construct(int $side) {
$this->side = $side;
$this->area = $side * $side;
}
function setSide(int $side) {
$this->side = $side;
$this->area = $side * $side;
}
}

$square = new Square(4);

echo $square->side; // 4
echo $square->area; // 16

?>

Здесь публичное свойство выступает в роли геттера/сеттера (или его отсутствия). Это имеет смысл для $side, но неудобно для свойства $area, которое на самом деле должно быть доступно для чтения из любого места, но для записи только с $side.

В PHP 8.4 Ilija Tovilo и Larry Garfield представили новую настройку видимости для записи. Буквально, видимость набора: private(set).

<?php

class Square {
public int $side;
public private(set) int $area;

function __construct(int $side) {
$this->side = $side;
$this->area = $side * $side; // Разрешено
}
}

$square = new Square(4);

echo $square->side; // 4
echo $square->area; // 16
$square->area = 15; // Запрещено!

?>

С этой новой настройкой свойство $area может быть прочитано с видимостью public: т.е., везде. А установить его можно только с помощью видимости private, т.е. изнутри класса. В данном случае это конструктор. И вуаля!

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

Какая видимость для каких свойств

Теперь с двумя наборами видимости PHP вводит не менее 9 различных конфигураций видимости. Это довольно много, и кажется, что декларацию видимости будет сложнее читать. Но не пугайтесь, впереди ждёт классическое улучшение удобства использования.

Итак, давайте посмотрим, удастся ли лучше понять все эти видимости.

Три симметричные видимости

Сначала рассмотрим симметричные возможности. В конце концов, private private(set), protected protected(set) и public public(set) - это симметричные видимости. Они были доступны с момента создания PHP (или почти… простите, Rasmus), и уже есть удобное сокращение для их написания: private, protected, public. Так что никаких изменений: они остаются краткими и обратно совместимыми!

Кроме того, новый токен private(set) (и его кузены) — это дополнение к языку. А это значит, что их можно использовать самостоятельно, без классической видимости чтения. А что происходит, когда видимость не указана? По умолчанию она становится public. Такое поведение сохранилось и в PHP 8.4, даже при явном задании асимметричной видимости.

<?php

class x {
private(set) int $p; // Отсутствие явной публичной видимости

function __construct() {
$this->p = 3;
}
}

$x = new x;
echo $x->p; // 3
$x->p = 2; // Невозможно изменить private(set) свойство x::$p из глобальной области видимости

?>

Три асимметричные видимости

Три асимметричные видимости: public private(set), protected private(set) и public protected(set). Они не являются симметричными (см. выше) и предоставляют отдельную область для чтения (public) и для записи (private(set)). Именно такой пример мы приводили в исходном классе Square.

Три нелегальные видимости

Последние три асимметричные видимости — это следующие: private public(set), private protected(set) и protected public(set). Это последние, в которых хост-класс не может записывать свои свойства, но при этом в них можно писать из любого места в приложении. Всё это не имеет смысла и запрещено PHP.

<?php

class x {
private public(set) int $p; // Отсутствие явной публичной видимости

function __construct() {
$this->p = 3;
}
}
//Fatal error: Visibility of property x::$p must not be weaker than set visibility

?>

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

Сводная таблица

privateprotectedpublic
private(set)СимметричноПолезноПолезно
protected(set)НелегальноСимметричноПолезно
public(set)НелегальноНелегальноСимметрично

Несколько предостережений

Асимметрия также и для статических свойств

Статические свойства также поддерживают асимметричную видимость.

<?php

class x {
static public private(set) string $p = 'a';

static function toggleP() {
x::$p = 'abc';
}
}

echo x::$p; // OK a

x::toggleP(); // Обновление
echo x::$p; // abc

x::$p = 'ab'; // Fatal error: forbidden
?>

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

Асимметричная видимость применима только к типизированным свойствам. Это ещё одна синтаксическая ошибка компиляции.

<?php

class x {
private private(set) $p = 2;
}
// Property with asymmetric visibility x::$p must have type
?>

Сообщение об ошибке в PHP 8.3

Очевидно, что у этого нового синтаксиса нет обратной совместимости. Это означает, что после перехода на асимметричную видимость вернуться к PHP 8.3 или более ранней версии будет невозможно. Сообщение об ошибке на самом деле интригует:

<?php

class x {
private private(set) int $p = 2;
}
// В PHP 8.3
// Multiple access type modifiers are not allowed
?>

Да, PHP не позволяет множить private private private $property сообщает об этом. Эта проверка была введена в PHP 5.3: тогда можно было сделать свойства действительно приватными, повторив ключевое слово. В современных версиях PHP эта фича была убрана, что пошло всем на пользу.

private(set), не чувствительно к регистру и без пробела

Последний забавный аспект нового токена — это его синтаксические ограничения. Это ключевое слово PHP, и, как таковое, оно не зависит от регистра. КЛЮЧЕВЫЕ СЛОВА В ВЕРХНЕМ РЕГИСТРЕ…

С другой стороны, он не толерантен к пробелам и выдаёт сообщение об ошибке PHP 8.3. Несложно запомнить. Однако привыкнув к гибкости добавления пробелов к операторам, об этом трудно не сожалеть.

<?php

class x {
// OK
private PRIVATE(SET) int $p = 2;

// KO
private PRIVATE( SET) int $p2 = 3;
// Multiple access type modifiers are not allowed
}

$a[] = 1;
$a [] = 1;
$a [ ]= 1;
$a []= 1;
$a [ /* даа... */ ] = 1;

?>

RFC, который стоит прочитать

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

Об этом слишком долго писать, а кроме того, RFC очень хорошо написан и детализирован. Ознакомьтесь с ним, а затем поблагодарите Ilija и Larry за огромную работу, проделанную ими над этим новым драгоценным камнем PHP.

А пока — счастливого аудита!

Пока же попробуйте новую асимметричную видимость свойств PHP и его новый синтаксис, скачав PHP 8.4 для тестирования или воспользовавшись сайтом https://3v4l.org/#live (выберите git.master в качестве версии PHP 8.4).

Комментарии


Дополнительные материалы

Предыдущая Статья

Новое в Symfony 7.2: Индикатор завершения работы консоли

Следующая Статья

Новое в Symfony 7.2: Улучшения ограничений