php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Request #71254 Allow limited recursion with __get().
Submitted: 2015-12-31 01:54 UTC Modified: 2015-12-31 02:11 UTC
Votes:2
Avg. Score:5.0 ± 0.0
Reproduced:2 of 2 (100.0%)
Same Version:1 (50.0%)
Same OS:0 (0.0%)
From: andreas at dqxtech dot net Assigned:
Status: Open Package: Class/Object related
PHP Version: 7.0.2RC1 OS:
Private report: No CVE-ID: None
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: andreas at dqxtech dot net
New email:
PHP Version: OS:

 

 [2015-12-31 01:54 UTC] 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;
    }
}


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2015-12-31 02:11 UTC] 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.
 [2016-03-05 23:04 UTC] 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.
 [2020-08-21 10:02 UTC] dk at dotnull dot de
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 :-)
 
PHP Copyright © 2001-2020 The PHP Group
All rights reserved.
Last updated: Thu Oct 29 23:01:23 2020 UTC