Предварительное увеличение в 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. Но я бы ожидал, что третий тест будет вести себя так же, как и второй, но это не так.
Я действительно этого не понимаю. Любая помощь будет очень признательна.
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()
может содержать код, который не имеет ничего общего с фактической операцией присваивания, и было бы потенциально серьезно неудобно, если бы этот код не выполнялся при изменении значения. Например, если бы нужно было реализовать какую-то архитектуру, управляемую событиями, которая запускала события при изменении определенных значений, было бы досадно, если бы это работало только с прямыми назначениями.