tags:

views:

175

answers:

3

This manages to create a new property on the object. But, can someone explain, with supporting links, why setAttrib behaves in two different ways? Why doesn't it cause a... wait for it... stack overflow!!??

class Test
{
  public function setAttrib( $key, $value ) {
    echo "setAttrib\n";

    // first time: calls $this->__set($key, $value)
    // second time: just sets a public property (but, when exactly was it created?)
    $this->$key = $value;
  }

  public function __set( $key, $value ) {
    echo "__set\n";
    $this->setAttrib($key, $value);
  }
}

$test = new Test();
$test->setAttrib('hey', 'It works');
var_dump($test);

produces...

setAttrib
__set
setAttrib
object(Test)#1 (1) {
  ["hey"]=>
  string(8) "It works"
}

Edit: I'm not looking for an alternative. I'm looking for the reason why this works.

+2  A: 

__set is only used when writing to inaccessible properties. That is, those who are not accessible (private or protected) or those that aren't set at all. Therefore, __set will only be called once.

Here's what happens:

  • setAttrib: Attempt to write
  • class: inaccessible property
  • __set: Do whatever __set is told to do, which is call setAttrib again.
  • setAttrib: Attempt to write
  • class: inaccessible property, but __set can't recurse, and we're already in it, so do it as if __set didn't exist.

See user comments for http://php.net/%5F%5Fset for proof that __set can't recurse.

Tor Valamo
I'm wondering, when exactly is the 'hey' member created? There's more than meets the eye here.
Derek Illchuk
It is written on the last run of setAttrib.
Tor Valamo
it happening in your very first call to set attribute. `__set` will never catch it unless you do `$a = new MyClass(); $a->hey = 'my text';` from within a class that doesnt have access to `MyClass::$hey`.
prodigitalson
Actually it does catch it, because the property isn't set. But __set isn't recursive, so it can only be called once per stack.
Tor Valamo
A: 

__set and __get only catch the call for the property if the member is protected. meaning that within the object $this->$key sets the property $key. if called from out of scope then the __set logic is called. One of your calls happens in the object outside the object.

prodigitalson
+4  A: 

You are not the only one who seems to have notice that non-recursive behaviour : this comment on the manual's page states :

2 - PHP will not recursively call one magic method from within itself (at least for the same $name).

And, a bit later on the same page, there is this one, which states :

The recursion detection feature can prove especially perilous when using __set. When PHP comes across a statement that would usually call __set but would lead to recursion, rather than firing off a warning or simply not executing the statement it will act as though there is no __set method defined at all.
The default behaviour in this instance is to dynamically add the specified property to the object thus breaking the desired functionality of all further calls to __set or __get for that property.


And, on PHP's bugtracker, there is #47215 : magic method __set() is bypassed on recursive call, which says :

Magic method __set() is bypassed on recursive call.
PHP automatically creates a property on instance instead of recursively calling __set() or instead of throwing a recursivity error

And it has been closed as :

Thank you for taking the time to write to us, but this is not a bug.

That bug-report, itself, points to this blog-post, which ends by this sentence (quoting, emphasis mine) :

After all I think it may not be a bug but expected behaviour, otherwise we could not be able to define object properties from within __set() method.

Pascal MARTIN