Предварительное увеличение в PHP с помощью magic get и set определено


У меня есть проблема, которая долгое время мешала мне делать то, что я хочу. Это связано с использованием magic get и set в PHP и попыткой выполнить предварительное увеличение над объектом. У меня есть класс PHP, подобный следующему:

class Foo {
    public $object;

    function __construct() {
            $this->object = array("bar" => 1);
    }

    function & __get($name) {
            return $this->object[$name];
    }

    function __set($name, $value) {
            echo "Old value: ". $this->object[$name] ." - New value: ". $value ."\n";
            $this->object[$name] = $value;
    }
}

Обратите внимание на & в методе __get. Теперь я запускаю этот код:

$o = new Foo();

echo "First test\n";
$o->bar = 2;

echo "Second test\n";
$o->bar = $o->bar + 1;

echo "Third test\n";
$o->bar += 1;

echo "Fourth test\n";
++$o->bar;

И вывод:

First test
Old value: 1 - New value: 2
Second test
Old value: 2 - New value: 3
Third test
Old value: 4 - New value: 4
Fourth test
Old value: 5 - New value: 5

Третий и четвертый тесты имеют неожиданное (для меня) поведение. Похоже, что $this->объект['панель'] возвращает значение, которое нужно установить, вместо этого старое значение, как я и ожидал. Как получилось, что значение уже установлено еще до того, как оно фактически установлено?

Если я удалю & из метода __get, это сработает, поэтому я предполагаю, что это как-то связано с управлением ссылками, которое выполняет PHP. Но я бы ожидал, что третий тест будет вести себя так же, как и второй, но это не так.

Я действительно этого не понимаю. Любая помощь будет очень признательна.

Author: JasonMArcher, 2012-05-09

1 answers

Результаты на самом деле (после тщательного рассмотрения) такие, каких я ожидал бы.


Первый тест

Код: $o->bar = 2;
Выход: Old value: 1 - New value: 2

Операции по порядку:

  • Присвоить новое значение $bar (вызов __set())

Очевидно, что это просто отбрасывает старое значение и ставит на его место новое значение. Ничего сложного.


Второе испытание

Код: $o->bar = $o->bar + 1;
Выход: Old value: 2 - New value: 3

Операции по порядку:

  • Получить копию $bar ( позвонить __get())
  • Добавьте к нему один
  • Присвоить новое значение $bar (вызов __set())

Вычисляется правая часть выражения, и результат присваивается левой стороне. Значение переменной экземпляра $bar не изменяется во время вычисления правой части, поэтому вы видите, что это старое значение в начале вызова __set().


Третий тест

Код: $o->bar += 1;
Выход: Old value: 4 - New value: 4

Операции по порядку:

  • Получить ссылку на $bar ( позвонить __get())
  • Добавьте к нему один
  • Присвоить новое значение $bar (вызов __set())

Это интересно - на первый взгляд я немного удивлен, что вы видите результат, сгенерированный __set() с помощью этой операции. Потому что вы увеличиваете значение, содержащееся в исходном zval, а не присваиваете совершенно новое значение - и поэтому совершенно новый zval - как и в случае с первыми двумя, кажется, что вызов __set() является избыточным.

Однако, когда __set() вызывается, вывод, который вы видите, является тем, что вы ожидаете, потому что значение , на которое ссылается , было увеличено до __set() был вызван. Значение, переданное в __set(), является результатом операции приращения, поэтому оно не приводит к добавлению к нему другого, оно просто присваивает значение $foo->bar $foo->bar - так что вы видите старое и новые значения как те же самые.


Четвертое испытание

Код: ++$o->bar;
Выход: Old value: 5 - New value: 5

...семантически идентично третьему тесту. Одни и те же операции в одном и том же порядке приводят к одному и тому же результату. Опять же, немного странно, что вообще есть какой-то выход, но вот и все.


Пятый тест, который я изобрел

Код: $o->bar++;
Выход: Old value: 5 - New value: 6

...семантически идентичный второму тест. Это снова интересно и немного неожиданно, но это также имеет смысл, учитывая, что $i++ возвращает старое значение $i.


Предположим, что причина, по которой __set() вызывается без необходимости в третьем и четвертом тестах, заключается в следующем:

  • (маловероятно, но возможно), потому что вычислительно было бы дороже определить, вернул ли __get() ссылку или копию, чем просто вызвать __set() - это кажется маловероятным, поскольку вызовы функций, как правило, довольно дорогой.
  • (более вероятно), потому что функция __set() может содержать код, который не имеет ничего общего с фактической операцией присваивания, и было бы потенциально серьезно неудобно, если бы этот код не выполнялся при изменении значения. Например, если бы нужно было реализовать какую-то архитектуру, управляемую событиями, которая запускала события при изменении определенных значений, было бы досадно, если бы это работало только с прямыми назначениями.
 6
Author: DaveRandom, 2012-05-09 17:07:16