Небольшой детектив вам на ночь в субботу.
Вы наверняка знаете, что наследники одного и того же базового класса могут читать друг у друга защищённые свойства, объявленные в этом базовом классе:
abstract class Father
{
protected string $data = 'x';
}
final class Son extends Father {}
final class Daughter extends Father
{
public function iCanSeeBrothersProtected(Son $brother): void
{
echo $brother->data;
}
}
// выведет x
new Daughter()->iCanSeeBrothersProtected(new Son());
Я, кстати, не могу согласиться с Сашей Макаровым, что это прям в чистом виде механизм дружественных классов в PHP. Всё-таки в других языках "дружба" декларируется специальным ключевым словом
friend между классами из несвязанных иерархий. В нашем случае это, скорее, "родственники". Но сейчас речь не об этом.На днях в internals Jonathan Vollebregt обратил внимание на интересное поведение защищённого свойства при его переопределении:
final class Son extends Father
{
// просто переопределяем свойство, ничего не меняя
protected string $data = 'x';
}
// и теперь выбрасывает Cannot access protected property Son::$data
new Daughter()->iCanSeeBrothersProtected(new Son());
Вопрос: это баг или нет?
Для начала надо понять, является ли вообще доступ к общим защищённым свойствам из родственных классов официальной фичёй PHP? В документации по видимости из фразы "members declared protected can be accessed only within the class itself and by inheriting and parent classes" такое поведение однозначным образом не следует. В других C-подобных языках такого тоже нет: C#, Kotlin.
Однако, пробежавшись по всем ссылкам статьи PHP friendly классы Саши Макарова, я нашёл тикет #37632 от мая 2006 года, который просит исправить отсутствие такой фичи как баг, и его исправляют в PHP 5.2! Далее в 2020 Никита Попов отвечает Саше в Твиттере, что такое поведение "looks fine" и не поменяется в будущем.
В таком случае кажется более логичным добавить в доку всю эту информацию, а также исправить текущее поведение при переопределении, чтобы всё было консистентно... Что вы думаете по этому поводу?
В любом случае я бы не рекомендовал таким пользоваться. Все эти игры с наследованием и видимостью оправданы разве что в недрах какого-нибудь фреймворка в классах с пометкой
@internal. В бизнесовом же проекте чем понятнее код, тем ниже вероятность, что его перепишут на Go. 😅