Search code examples
phpmagic-methods

PHP - Indirect modification of overloaded property


I know this question has been asked several times, but none of them have a real answer for a workaround. Maybe there's one for my specific case.

I'm building a mapper class which uses the magic method __get() to lazy load other objects. It looks something like this:

public function __get ( $index )
{
    if ( isset ($this->vars[$index]) )
    {
        return $this->vars[$index];
    }

    // $index = 'role';
    $obj = $this->createNewObject ( $index );

    return $obj;
}

In my code I do:

$user = createObject('user');
$user->role->rolename;

This works so far. The User object doesn't have a property called 'role', so it uses the magic __get() method to create that object and it returns its property from the 'role' object.

But when i try to modify the 'rolename':

$user = createUser();
$user->role->rolename = 'Test';

Then it gives me the following error:

Notice: Indirect modification of overloaded property has no effect

Not sure if this is still some bug in PHP or if it's "expected behaviour", but in any case it doesn't work the way I want. This is really a show stopper for me... Because how on earth am I able to change the properties of the lazy loaded objects??


EDIT:

The actual problem only seems to occur when I return an array which contains multiple objects.

I've added an example piece of code which reproduces the problem:

http://codepad.org/T1iPZm9t

You should really run this in your PHP environment the really see the 'error'. But there is something really interesting going on here.

I try to change the property of an object, which gives me the notice 'cant change overloaded property'. But if I echo the property after that I see that it actually DID change the value... Really weird...


Solution

  • Nice you gave me something to play around with

    Run

    class Sample extends Creator {
    
    }
    
    $a = new Sample ();
    $a->role->rolename = 'test';
    echo  $a->role->rolename , PHP_EOL;
    $a->role->rolename->am->love->php = 'w00';
    echo  $a->role->rolename  , PHP_EOL;
    echo  $a->role->rolename->am->love->php   , PHP_EOL;
    

    Output

    test
    test
    w00
    

    Class Used

    abstract class Creator {
        public function __get($name) {
            if (! isset ( $this->{$name} )) {
                $this->{$name} = new Value ( $name, null );
            }
            return $this->{$name};
        }
    
        public function __set($name, $value) {
            $this->{$name} = new Value ( $name, $value );
        }
    
    
    
    }
    
    class Value extends Creator {
        private $name;
        private $value;
        function __construct($name, $value) {
            $this->name = $name;
            $this->value = $value;
        }
    
        function __toString()
        {
            return (string) $this->value ;
        }
    }      
    

    Edit : New Array Support as requested

    class Sample extends Creator {
    
    }
    
    $a = new Sample ();
    $a->role = array (
            "A",
            "B",
            "C" 
    );
    
    
    $a->role[0]->nice = "OK" ;
    
    print ($a->role[0]->nice  . PHP_EOL);
    
    $a->role[1]->nice->ok = array("foo","bar","die");
    
    print ($a->role[1]->nice->ok[2]  . PHP_EOL);
    
    
    $a->role[2]->nice->raw = new stdClass();
    $a->role[2]->nice->raw->name = "baba" ;
    
    print ($a->role[2]->nice->raw->name. PHP_EOL);
    

    Output

     Ok die baba
    

    Modified Class

    abstract class Creator {
        public function __get($name) {
            if (! isset ( $this->{$name} )) {
                $this->{$name} = new Value ( $name, null );
            }
            return $this->{$name};
        }
    
        public function __set($name, $value) {
            if (is_array ( $value )) {
                array_walk ( $value, function (&$item, $key) {
                    $item = new Value ( $key, $item );
                } );
            }
            $this->{$name} = $value;
    
        }
    
    }
    
    class Value {
        private $name ;
        function __construct($name, $value) {
            $this->{$name} = $value;
            $this->name = $value ;
        }
    
        public function __get($name) {
            if (! isset ( $this->{$name} )) {
                $this->{$name} = new Value ( $name, null );
            }
    
            if ($name == $this->name) {
                return $this->value;
            }
    
            return $this->{$name};
        }
    
        public function __set($name, $value) {
            if (is_array ( $value )) {
                array_walk ( $value, function (&$item, $key) {
                    $item = new Value ( $key, $item );
                } );
            }
            $this->{$name} = $value;
        }
    
        public function __toString() {
            return (string) $this->name ;
        }   
    }