PHP pre-increment using get and set defined magic

I have a problem that messed up the way I want to do things for a long time. This is due to the use of get and set magic in PHP and tries to pre-increment the object. I have a PHP class, for example:

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; } } 

Note the & in the __get method. Now I run this code:

 $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; 

And the result:

 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 

The third and fourth tests have unexpected (for me) behavior. It looks like $ this-> object ['bar'] returns the value to be set, instead of the old value, as I expected. Why is the value already set before it is actually set?

If I remove & from the __get method, this will work, so I think this has something to do with the link management that PHP does. But I expect the third test to behave the same as the second, but it is not.

I really don't get it. Any help would be greatly appreciated.

+4
source share
1 answer

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.
+6
source

Source: https://habr.com/ru/post/1411575/


All Articles