Req #71254 [Com]: Allow limited recursion with __get().
Edit report at https://bugs.php.net/bug.php?id=71254&edit=1
ID: 71254
Comment by: dk at dotnull dot de
Reported by: andreas at dqxtech dot net
Summary: Allow limited recursion with __get().
Status: Open
Type: Feature/Change Request
Package: Class/Object related
PHP Version: 7.0.2RC1
Block user comment: N
Private report: N
New Comment:
Hello folks! We ran into the same issue...
We instantiate a new Class and save it to an static array inside the __get:
static::$objects[$class] = $object
After that any call to ->Classname inside the __get or any recursed
function/method stops and returns an error as explained by andreas. When checked with
isset($this->Classname) it results to true, but a direct access with
$this->Classname in an error...
This is indeed a bug! I know that this has been made to prevent circular calls to __get, but it is
somehow confusing.
Maybe one way is that PHP should call __isset($var) and validate that the variable is
set, and if set PHP should return __get($var), otherwise the error...
Another way would be to implement a new __exists() magic method? Therefor a new
exists() function analogue to isset() is needed, that checks if the
variable is declared independent of the variables value... ALSO NULL!
So a $var = null; is a declaration and exists($var) should return true!
And an array $data[$var] = null with exists($data[$var]) should also
return true!
An __exists($var) can then return true of
array_key_exists($var,static::$objects) returns true... Or in simple if
exists(static::$objects[$var]) is true...
This would be a nice optimization. The functions array_key_exists() or functions like
property_exists will not be needed anymore...
Thank you, please fix this :-)
Previous Comments:
------------------------------------------------------------------------
[2016-03-05 23:04:24] yen1 at senam dot cz
I ran into a similar issue :(. I don't know a purpose why prevent recursion when magic __get /
__set can be called manually and thus still fall into infinite recursion? It is also a bit confusing
when the __get method gets called for the first time but not for the second time.
------------------------------------------------------------------------
[2015-12-31 02:11:09] andreas at dqxtech dot net
Hmm.. maybe the correct solution is not a stub, but a proxy (lazy instantiation).
And with that, every key in __get() is only called once.
A single proxy in the loop is sufficient to prevent the recursion and all problems caused by it.
A single stub in the loop is sufficient to prevent infinite recursion. But to also avoid duplicate
values in the buffer, one needs one stub per calc_*() method..
There is a trick to only need one stub in the circle:
https://3v4l.org/nXTYJ
But I'm not sure if I really like this, or if proxy is generally better.
------------------------------------------------------------------------
[2015-12-31 01:54:26] andreas at dqxtech dot net
Description:
------------
Currently, magic __get() refuses to dive into recursion if the same key is repeated.
https://3v4l.org/00Kuv
It stops, and gives a "Notice: Undefined property: C::$x".
There are cases where the recursion is not a bug, and it is indeed legitimate to dive one level into
the recursion.
As we can see, calling ->__get('x') explicitly instead of magic ->x, avoids the
notice. But this is undesirable, because we want the IDE to recognize that we are calling @property
$x.
The unlimited recursion can be avoided with stubs:
https://3v4l.org/Nb3VF
Proposal:
I want to propose that the recursion detection mechanic only gets active if the same key is used a
3rd time, not the 2nd time. This permits the legitimate case, but still prevents unlimited
recursion.
Test script:
---------------
<?php
class C {
private $buffer = array();
function __get($key) {
if (array_key_exists($key, $this->buffer)) {
return $this->buffer[$key];
}
$f = 'calc_' . $key;
return $this->buffer[$key] = $this->$f();
}
function calc_x() {
$x = new stdClass;
// Set a stub to avoid infinite recursion.
$this->buffer['x'] = $x;
$x->y = $this->y;
return $x;
}
function calc_y() {
$y = new stdClass;
// Set a stub to avoid infinite recursion.
$this->buffer['y'] = $y;
$y->x = $this->x;
return $y;
}
}
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=71254&edit=1
Thread (5 messages)