Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP: __set function behaviour different each time

Tags:

php

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.

like image 630
Derek Illchuk Avatar asked Dec 30 '22 07:12

Derek Illchuk


2 Answers

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.

like image 91
Pascal MARTIN Avatar answered Jan 12 '23 12:01

Pascal MARTIN


__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/__set for proof that __set can't recurse.

like image 31
Tor Valamo Avatar answered Jan 12 '23 12:01

Tor Valamo