Магический геттер/сеттер не вызывается
Когда я читал главу ООП в учебном руководстве по сертификации 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 и отображается значение пары ключей.