Req #71254 [Com]: Allow limited recursion with __get().

From: Date: Fri, 21 Aug 2020 10:02:17 +0000
Subject: Req #71254 [Com]: Allow limited recursion with __get().
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-228701@lists.php.net to get a copy of this message
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)

« previous php.bugs (#228701) next »