Магический геттер/сеттер не вызывается

Когда я читал главу ООП в учебном руководстве по сертификации Zend PHP Certification 5.5, я нашел вопрос, который дал мне шок от его ответа. Этот вопрос:

class Magic
{
    public $a = "A";
    protected $b = array( "a" => "A" , "b" => "B" , "c" => "C" );
    protected $c = array( 1 , 2 , 3 );

    public function __get( $v )
    {
        echo "$v";
        return $this->b[$v];
    }

    public function __set( $var , $val )
    {
        echo "$var: $val,";
        $this->$var = $val;
    }
}

$m = new Magic();
echo $m->a . ", " . $m->b . ", " . $m->c . ", ";
$m->c = "CC";
echo $m->a . ", " . $m->b . ", " . $m->c . ", ";

Выход для этого кода:

b, c, A, B, C, c: CC, b, c, A, B, C

Почему этот код не печатает a и как он работает?

Ответ 1

__get используется только для несуществующих или невидимых свойств. Другими словами, когда вы пишете

$obj->prop

если prop определен и отображается в текущем контексте, он будет возвращен "как есть", не вызывая __get.

Пример:

class X {

    public   $pub = 1;
    private  $pri = 2;

    function __get($v) {
        echo "[get $v]\n";
        return 42;
    }

    function test() {
        echo $this->foo, "\n";  // __get invoked
        echo $this->pri, "\n";  // no __get
        echo $this->pub, "\n";  // no __get

    }
}

$x = new X;
$x->test();

echo $x->foo, "\n"; // __get invoked
echo $x->pri, "\n"; // __get invoked (property not visible)
echo $x->pub, "\n"; // no __get

Это объясняет, почему magic->a не вызывает геттер. Теперь, поскольку у вас также установлен определитель, magic->c = CC фактически изменяет защищенный член класса, поэтому, когда вы снова эхо magic->c, это все равно вызывает геттер (из-за c невидимости), а получатель возвращает this->b[c], а не фактическое значение this->c.

Здесь ваш код, слегка переписанный для ясности:

class Magic
{
    public $a = "publicA";
    protected $values = array( "a" => "valA" , "b" => "valB" , "c" => "valC" );
    protected $c = "oldC";

    public function __get( $v )
    {
        echo "[get $v]\n";
        return $this->values[$v];
    }

    public function __set( $var , $val )
    {
        echo "[set $var=$val]\n";
        $this->$var = $val;
    }
}

$m = new Magic();
echo $m->a . ", " . $m->b . ", " . $m->c . "\n";
$m->c = "newC";
echo $m->a . ", " . $m->b . ", " . $m->c . "\n";

Результаты:

[get b]      
[get c]
publicA, valB, valC  # no getter for `a` 
[set c=newC]
[get b]
[get c]              # getter still invoked for `c`
publicA, valB, valC  # no getter for `a`

Упражнение для читателя:

Почему вывод отличается, если вы заменяете точки . в операторах эха запятыми:

$m = new Magic();
echo $m->a , ", " , $m->b , ", " , $m->c , "\n";
$m->c = "newC";
echo $m->a . ", " , $m->b , ", " , $m->c , "\n";

Ответ 2

Собственно, вот что печатается ( demo):

bcA, B, C, c: CC,bcA, B, C,

Точка, каждая часть выражения $m->a . ", " . $m->b . ", " . $m->c . ", " оценивается до того, как результат будет напечатан echo. В этом случае существует побочный эффект такой оценки.

Как показал @georg, это было бы совсем иначе, если бы код был написан как echo $m->a, ', ', $m->b, ', ', $m->c, ', ' вместо этого: в этом случае каждый операнд был бы отправлен на вывод сразу после его оценки!

$m->a часть легко оценить, поскольку a является публичным свойством; его значением является строка 'A'.

Однако часть

$m->b является сложной: b является защищенным свойством, поэтому следует называть магический метод __get(). Этот метод печатает b (имя доступного свойства) и возвращает b (значение $this->b['b']). Вот почему вы видите, что ваша строка начинается с b - она ​​печатается до того, как оценивается целое выражение!

То же самое происходит и с $m->c, так как c также является защищенным свойством: геттерный шрифт печатает c, но возвращает c (значение $this->b['c']), который будет напечатан как часть целое выражение.

После этого обрабатывается строка $m->c = "CC", вызов другой - метод setter-magic (как c защищен, помните). Этот метод является тем, что печатает c: CC,, поскольку $var равно имени установленного свойства, а $val - его новое значение (передано в __set).

Но хотя волшебный сеттер в конечном итоге изменяет значение свойства c, следующая строка в коде выводит ту же строку, что и раньше. Это потому, что волшебный геттер никогда не получает доступ к $this->c - вместо этого возвращается значение $this->b['c'] при запросе для c.


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

Ответ 3

Полученный результат не соответствует тому, что я получил:

bcA, B, C, c: CC,bcA, B, C,

Тем не менее, этот класс демонстрирует overloading с помощью магических методов __get() и __set(), с которыми вы столкнулись. Я думаю, что причиной вашей путаницы может быть порядок, в котором выходили письма.

Поскольку магический метод __get() содержит echo фактического имени свойства, вы могли получить abc, если бы не эта небольшая деталь:

/**  Overloading is not used on declared properties.  */
public $a = "A";

Таким образом, поскольку выражение не получает доступ к магическому методу для свойства a, оно будет не печатать имя свойства, вместо этого оно печатает значение. Обратите внимание на порядок выполнения функций echo. echo в методе getter magic выполняется перед эхом вне класса.

Примечание:

PHP-интерпретация "перегрузки" отличается от большинства объектов ориентированных языков. Перегрузка традиционно предоставляет возможность имеют несколько методов с тем же именем, но разные количества и типы аргументов.

Ответ 4

Это из-за вашего метода __get в классе. Он всегда вызывает метод get и вызывает значения массива $b.

public function __get( $v )
{
    echo "$v";
    return $this->b[$v];
}

Каждый раз, когда вы вызываете $m->a . ", " . $m->b . ", " . $m->c . ", ";, он запускает метод __get и отображает значение пары ключей.

Аналогично,

$m->c = "CC";

запускает метод __set и отображает установленное значение.

public function __set( $var , $val )
{
    echo "$var: $val,";
    $this->$var = $val;
}

echo $m->a . ", " . $m->b . ", " . $m->c . ", ";

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