Доступ к protected свойствам объектов с общим предком

На днях я понял нечто странное о доступе к protected свойствам. В PHP возможно получить доступ к protected свойствам из других объектов, пока они того же класса, как проиллюстрировано ниже:


<?php

class MyClass 
{
    protected $val;

    public function __construct($newVal = 'default') 
    {
        $this->val = $newVal;
    }

    public function output(MyClass $subject) 
    {
        echo $subject->val, "\n";
    }
}

$obj1 = new MyClass();
$obj2 = new MyClass("hello world");

$obj1->output($obj2);
// Output: hello world

Я всегда думал, что protected позволяет объектам получить доступ к элементам строго от текущего дерева наследования, но не понимал, что это распространяется и на другие экземпляры того же объекта.

Такое поведение работает на свойствах и методах даже тогда, когда область видимость их определяются как private.

На другой день, однако, я понял, что это также работает и для объектов других классов, в которых вы обращаетесь к членам, объявленным в классе с общим предком.

Звучит немного сложно, так что вот пример:


<?php

class Ancestor 
{
    protected $val = 'ancestor';
}

class Child1 extends Ancestor 
{
    public function __construct() 
    {
        $this->val = 'child1';
    }
}

class Child2 extends Ancestor 
{
    public function output(Ancestor $subject) 
    {
        echo $subject->val, "\n";
    }
}

$child1 = new Child1();
$child2 = new Child2();

$child2->output($child1);
// Output: child1

Любопытно, что если прошлый пример изменить так, чтобы свойства не были установлены в конструкторах, а вместо этого переопределить свойство, то это вызовет ошибку:


<?php

class Ancestor 
{
    protected $var = 'ancestor';
}

class Child1 extends Ancestor 
{
    protected $var = 'child1';
}

class Child2 extends Ancestor 
{
    public function output(Ancestor $subject) 
    {
        echo $subject->var, "\n";
    }
}

$child1 = new Child1();
$child2 = new Child2();

$child2->output($child1);
// Output: Fatal error: Cannot access protected property Child1::$var

Поскольку третий пример выдаёт ошибку, а второй нет, это заставляет меня ощутить, будто я просто наткнулся на крайность движка PHP (edge-case), и что эта фича не была задумана специально. Иначе оба варианта должны были бы работать, на мой взгляд.

В конце концов, принцип подстановки Барбары Лисков диктует, что если определённое поведение (в данном случае, свойство) работает для предка, то оно также должно работать для любых подклассов.

Кроме того, PHP обычно позволяет вам изменять область видимости свойств, чтобы сделать их более заметными.

Ещё я столкнулся со случаем, когда поведение примера №2 супер удобно. Но я не до конца уверен, хорошая ли эта идея, положиться на такое поведение или стоит предположить, что это просто «undefined» и может быть изменено без уведомления в последующей версии PHP.

Всё же, спецификация PHP явно не запрещает так делать. Соответствующий текст:

К члену с protected видимостью можно получить доступ только из его собственного класса и от классов, полученных из этого класса. Источник.

Конечно, спецификация PHP ещё, вероятно, не закончена, и я не уверен, что данный случай исследуется командой PHP вообще.

Так что я остаюсь в сомнениях, доверять ли такому поведению. Но, по крайней мере, это хороший пример того, почему наличие правильного и официального стандарта PHP является хорошей идеей.

Примечание

Это авторский перевод статьи «Accessing protected properties from objects that share the same ancestry.» на русский язык.

Комментарии

  1. Includen пишет:

    forsaken1 поделился вариантом на Ruby: https://gist.github.com/forsaken1/f0304a6d1cfc519d1e6c . Основной код соответствует второму примеру и ошибок не выдаёт. Третий пример также как и в PHP вызывает ошибку.

  2. Можно ещё проще, не заморачиваясь.

    $object = new Foo();
    $bar = (array) $object();
    // var_dump($bar);

  3. Includen пишет:

    Вот объяснение от Кирилла Кулева:

    Всё очень просто.
    Во 2м примере вы на самом деле оперируете не родным св-м класса, а унаследованным, т.е родителя. К этому же св-ву имеют доступ и другие дети родителя.

    В 3м примере вы переопределяете св-во для текущего класса. Не смотря на то, что видимость созраняется как protected, св-во начинает принадлежать уже не родителю, а наследнику. По этой причине параллельный брат класса не может получить к нему доступ.

    Доступ между детьми можно получать только если эти св-ва связанны как унаследованные от общего предка. Я изучал ранее эту проблему сам. Баг тут только в 1 и баг этот в ядре пхп — так же доступ можно получать и к приватным методам.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *