The results are actually (after careful consideration) what I expect.
First test
Code: $o->bar = 2;
Output: Old value: 1 - New value: 2
Operations in order:
- Assign a new value to
$bar
(call __set()
)
Obviously, this simply leaves the old meaning and puts the new value in its place. Nothing complicated.
Second test
Code: $o->bar = $o->bar + 1;
Output: Old value: 2 - New value: 3
Operations in order:
- Get a copy of
$bar
(call __get()
) - Add to it
- Assign a new value to
$bar
(call __set()
)
The right side of the expression is evaluated, and the result is assigned to the left side. The value of the $bar
instance variable is not affected when evaluating the right side, so you see this old value at the beginning of the __set()
call.
Third test
Code: $o->bar += 1;
Output: Old value: 4 - New value: 4
Operations in order:
- Get a link to
$bar
(call __get()
) - Add to it
- Assign a new value to
$bar
(call __set()
)
This is interesting - at first glance I am a little surprised that you see the result generated by __set()
with this operation. Since you are increasing the value stored in the original zval without assigning a completely new value - and therefore a whole new zval - as in the first two, it seems that calling __set()
is redundant.
However, when __set()
is called the result you see, this is what you expect, because the specified value was incremented before __set()
was called. The value passed to __set()
is the result of the increment operation, so it does not add another, it just assigns the value $foo->bar
$foo->bar
- so you see the old and new values ββas one and the same same.
Fourth test
Code: ++$o->bar;
Output: Old value: 5 - New value: 5
... semantically identical to the third test. Exactly the same operations in the same order, which leads to an exact exit. Again, itβs a little strange that there is some kind of conclusion, but there you go.
The fifth test I came up with
Code: $o->bar++;
Output: Old value: 5 - New value: 6
... semantically identical to the second test. This is interesting again and a bit unexpected, but it also makes sense, given that $i++
returns the old value of $i
.
When guessing, the reason why __set()
is called without the need for the third and fourth tests:
- (unlikely, but possible) because it would be more expensive to calculate whether
__get()
return a link or a copy than just calling __set()
- this seems unlikely since function calls are usually quite expensive. - (more likely) because the
__set()
function may contain code that has nothing to do with the actual assignment operation, and would be potentially seriously inconvenient if this code was not executed when the value was changed. For example, if someone were to implement some kind of event-driven architecture that would trigger events when some values ββwere changed, it would be very unpleasant if it worked only with direct assignments.