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:3
Avg. Score:5.0 ± 0.0
Reproduced:3 of 3 (100.0%)
Same Version:2 (66.7%)
Same OS:1 (33.3%)
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
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
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

Pull Requests

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 :-)
 [2023-05-02 11:17 UTC] hojadeepk at gmail dot com
Auto Web Reviews are sharing latest news about auto, electric bike and scooters, trucks, bus, automobile, auto reviews etc. More info to visit our website: (https://autowebreviews.com)github.com
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sat Dec 14 02:01:27 2024 UTC