skip to Main Content
class Test
{
    public $prop1;
    public function __get(string $n)
    {
        echo '__GET: '.$n.' - but why?<br>';
        die;
    }
}

$t = new Test();
$x1 = new stdClass();
$t->prop2 = &$x1;
echo '.';
die;

https://onlinephp.io/c/f7f16

here you can see, I create an stdClass object, try to pass it to a non-exists variable, and __get() gets executed – even though I was just tryting to write it, instead of reading. And if I didn’t die() it, I get Indirect modification of overloaded property has no effect exception.

If I have this (no reference)

class Test
{
    public $prop1;
    public function __get(string $n)
    {
        echo '__GET: '.$n.' - but why?<br>';
        die;
    }
}

$t = new Test();
$x1 = new stdClass();
$t->prop2 = $x1;
echo '.';
die;

https://onlinephp.io/c/3afef

everything works fine, but why?

4

Answers


  1. When you assign by reference1, the left hand variable needs to be defined (it exists) and if it doesn’t (it is undefined), PHP will define it.

    However not in the $t->prop2 case:

    The variable’s member name ($t->prop2) is undefined but the __get()2 magic method of $t dictates PHP to not define it as it would normally do, but instead pass on to __get() as first parameter the members’ name, to obtain its overloaded value.

    This is flawed, as to assign by reference requires the variable (or property) to be defined for a successful operation. But undefined (overloaded) can not fulfill this.

    Therefore the warning:

    Indirect modification of overloaded property stdClass@anonymous::$prop2 has no effect

    and finally the fatal error:

    Cannot assign by reference to overloaded object

    Example Code3,4

    $t = new class() extends stdClass
    {
        public function __get(string $name) {
            $x = null;
            return $x;
        }
    };
    
    $t->prop2 = &$x1;
    

    1. Assignment by Reference – PHP Manual
    2. Property overloading – PHP Manual
    3. Example code on 3v4l.org
    4. Backwards compatible example code on 3v4l.org for historic view
    Login or Signup to reply.
  2. Per documentation:

    __get() is utilized for reading data from inaccessible (protected or private) or non-existing properties.

    if you want to use __get by reference, here what you should write:

    public function &__get ( $index )
    
    Login or Signup to reply.
  3. Internally, PHP has a concept of ‘reading’ a property in order to perform a write/modify operation. Though it may seem incoherent at first glance, this allows PHP to perform certain in-place modification operations with less overhead, that would otherwise have to rely on read-modify-write round-trips. Situations where it is used include appending to an array and creating a new reference to the property. For this to work with dynamically-resolved properties, the __get magic method must return a reference, which is to say, a place which can have its value modified; otherwise a notice will be triggered with the message ‘Indirect modification of overloaded property has no effect’.

    class Globalses {
        public function& __get(string $name) {
            echo "{$name} GET!n";
            return $GLOBALS[$name];
        }
    }
    
    $obj = new Globalses;
    
    $foo = [0, 1, 2, 3];
    $obj->foo[] = 4;     // foo GET!
    echo 'foo='; debug_zval_dump($foo);
    
    $bar = 42;
    $var =& $obj->bar;   // bar GET!
    $var = 69;
    echo 'bar='; debug_zval_dump($bar);
    

    You will get this message in the above example if you remove the & from the declaration of function& __get, which will make the method return a value, and not a reference.

    The same code path of ‘reading-in-order-to-modify’ is triggered when an object property is on the left-hand side of reference-assignment (=&). But of course, if property lookup is performed dynamically by user code (what PHP misnames ‘overloading’), there is no way PHP can promise that ‘after $obj->prop =& (expr) is executed, accessing $obj->prop will resolve to the same thing (expr) did at that time’, so PHP throws an exception. However, the interpreter realises this only after the __get magic method has already been invoked.

    This can be seen by inspecting the source code of the Zend engine. To evaluate an expression like $obj->prop =& expr, the Zend engine calls the function zend_assign_to_property_reference. This function invokes zend_fetch_property_address in order to determine which zval (an internal representation of a PHP value) to modify so that it becomes an alias for the same zval as the right-hand side expression. It does so by invoking the get_property_ptr_ptr internal method slot of the object; if that returns nothing, there is a fallback to the read_property slot. For user-code objects, this means invoking the zend_std_read_property function, which is where the call to the __get magic method is actually made.

    Again, because zend_std_read_property is called with flags that signify it is being ‘read-in-order-to-modify’, if __get happens not to return a reference (because it is not declared function& __get), an E_NOTICE error is raised. But either way, zend_std_read_property will return its result in the zval prepared for it by zend_fetch_property_address instead of a pre-existing one, which causes the latter not to wrap it in a so-called indirect zval as zend_assign_to_property_reference expects, which causes that function in turn to throw an exception.

    This is a pretty baroque implementation, if you ask me. I even agree that the Zend engine could have been implemented so that by the point __get is about to be called, the interpreter realises it’s actually pointless to invoke and throws an exception immediately. But given that this code path is hit only in user code that is incorrect and should be rewritten, perhaps it makes little sense to optimise.

    If you compiled the list of things in PHP that make no sense, I’m not sure this would even make the top ten; the language is just that bizarre. Get used to it. Or don’t: changing the language is also an option.

    Login or Signup to reply.
  4. There are a lot of complex explanations here is a simple answer as to why it’s calling __get(),

    $obj = new stdClass();
    $test = "hello";
    $obj->var = &%test;
    

    Take the simple example above, to be able to set $obj->var to the memory address that $test points to, $obj->var needs a memory address inside $obj, without __get PHP will just create one on its own per the code above if your using __get PHP won’t and needs you to define it. the only place you could do that definition is inside that __get

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search