php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Doc Bug #28444 "Cannot access undefined property for object with overloaded property access"
Submitted: 2004-05-19 12:34 UTC Modified: 2005-01-18 10:06 UTC
Votes:61
Avg. Score:4.8 ± 0.6
Reproduced:61 of 61 (100.0%)
Same Version:17 (27.9%)
Same OS:20 (32.8%)
From: dennis at inmarket dot lviv dot ua Assigned:
Status: Closed Package: Documentation problem
PHP Version: 5.0.0RC2 OS: WinXP
Private report: No CVE-ID:
 [2004-05-19 12:34 UTC] dennis at inmarket dot lviv dot ua
Description:
------------
"Cannot access undefined property for object with overloaded property access" is an error when I try to set a property of an instance, which, in turn, is a property of another instance of an overloaded class, i.e. $a->x->y = 2 triggers the error if $a is an instance of an overloaded class. Note that getting that property works fine.

Reproduce code:
---------------
class Object {
  public $x;
  function __construct($x) {
    $this->x = $x;
  }
}
class Overloaded {
  var $props;
  function __construct($x) {
    $this->x = new Object($x);
  }
  function __get($prop) {
    return $this->props[$prop];
  }
  function __set($prop, $val) {
    $this->props[$prop] = $val;
  }
}
$y = new Overloaded(2);
echo $y->x->x, " "; // Prints 2...
echo $y->x->x = 3; //Should print 3...

Expected result:
----------------
2 3

Actual result:
--------------
2
Fatal error: Cannot access undefined property for object with overloaded property access in path/to/script.php on line 22

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2004-05-27 12:15 UTC] stas@php.net
The problem here is that it is unclear what actually should happen in this case. When executing $y->x->x = 3, we need to fetch $y->x for writing, i.e., get zval** of it. However, only handler that could give it to us is get_property_ptr_ptr  and it is not overridable. 
We probably could use the result of __get, though it would mean we would have to fake zval**, which could have some strange consequences - needs to be checked.
 [2004-05-27 16:11 UTC] dennis at inmarket dot lviv dot ua
Well, I am not familiar with the engine code, but it works for getting "undefined property for object with overloaded property access", as the reproduce code clearly shows.
 [2004-06-14 20:24 UTC] cunha17 at uol dot com dot br
This odd behavior is still present in latest CVS and happens in Linux also.

Cristiano Duarte
 [2004-06-30 04:21 UTC] ryan dot harris at gmail dot com
A good solution would be to have a way of doing a "pre-set" routine i.e. __get($propertyname, $pre_set = true) and that routine is expected to return a valid object or the "Cannot access undefined..." will automatically result.

Some more example code that causes the problem (no way of testing to see if the concept is correct - since this message keeps appearing):

class Setting {
	private $mSettings;
	
	function __construct() {
		$constructor_args = func_get_args();
		
		if (isset($constructor_args[0])) {
		    $this->mSettings = array("_defaultValue"=>$constructor_args[0]);
		} else
			$this->mSettings = array("_defaultValue"=>null);
	}
	
	function __get($propertyName) {
		print "[$propertyName]<br>\n";
		if ($propertyName == "_settingCount") {
		    return count($this->mSettings) - 1;
		} elseif ($propertyName == "_valueNames") {
		    //return  $this->GetValueNames();
		} elseif ($propertyName == "_keyNames") {
		    //return  $this->GetValueNames();
		} elseif ($propertyName == "_defaultValue") {
			print_r($this->mSettings);
			return $this->mSettings["_defaultValue"];
		}
		
		// It's none of the special properties.
		if (isset($this->mSettings[$propertyName])) {
			return $this->mSettings[$propertyName];
		} else {
                        // If we had the $pre_set we could do this:
                        if ($pre_set) {
                            // initialize for a set
                            $this->mSettings[$propertyName] = new Setting();
                            return $this->mSettings[$propertyName];
                        } else 
			throw new Exception("Undefined setting name.");
		}
	}
	
	function __set($propertyName, $propertyValue) {
		print "[$propertyName]<br>\n";
		switch ($propertyName) {
		    case "_settingCount":
			case "_valueNames":
			case "_keyNames":
				throw Exception("Property is read only.");
			    break;
			case "_defaultValue":
				$this->mSettings["_defaultValue"] = $propertyValue;
			    break;
			default:
			    if (!isset($this->mSettings[$propertyName]))
					$this->mSettings[$propertyName] = new Setting($propertyValue);
				else
					$this->mSettings[$propertyName]->_defaultValue = $propertyValue;
			    break;
		} // switch
	}
}


This code if it worked would let you do the following:

$t = new Setting();
$t->includes->_defaultValue = "Automated"
$t->includes->automatedIncludes->includeDirectory = "c:/includes"
$t->includes->automatedIncludes = "oncePerFile"
$t->includes->manualIncludes->includeDirectory = "c:/php"

Which would represent:
includes
  = Automated
  --> automatedIncludes
      = oncePerFile
      --> includeDirectory
          = c:/includes
  --> manualIncludes
      --> includeDirectory
          = c:/php

in a lightweight manner.  The layers would be created as they are needed and all with an elegant method.
 [2004-08-05 06:48 UTC] alexei at net24 dot co dot nz
it allows to read value referenced by $a->b->c
but it does not allow to modify it, that's makes the whole thing inconsistent...

workaround like below does the job, but it is ugly!
$b=$a->b;
$b->c=5;

i think if ->-> is called it should call __get methods at
all each stage and pass reference to the next -> operation
 [2004-08-05 07:20 UTC] alexei at net24 dot co dot nz
another "workaround", it allows to use this feature now and
to convert scripts later when this feature is available in php.

class a {

function __call ($property, $args){
return __get($property);
}

use it like this:

$a->b->c=10; as $a->b()->c=10;
echo $a->b->c; as echo $a->b()->c;
 [2004-08-05 10:52 UTC] dennis at inmarket dot lviv dot ua
In my opinion, such "workarounds" should be replaced by fixing the issue in PHP itself. Indeed, it is funny that this works for getting properties, and doesn't for setting. Onother question: how did PHP5 make for a release with this core level bug?
 [2004-08-05 11:04 UTC] derick@php.net
There are plenty of other non fixed bugs. If we were to release when the bug count reaches zero we would never release.
 [2004-08-05 11:19 UTC] dennis at inmarket dot lviv dot ua
To derick@php.net: Please don't feel hurt by this comment. I think you guys did a great job on making PHP5 what it is, but this bug is different. I think (but I mon sure, since I don't get anything in the PHP engine code) this should fix very well.
 [2004-08-21 03:21 UTC] matth at alsync dot com
Another work around for this is:

a->b->__set('c', 'value');

At least the underlying calsses do not need to be changed to make this work.
 [2004-10-13 14:05 UTC] info at pandora-web dot de
Another Test Case:
---------------
class TestClass {
    private $_p = array();
    
    public function __get($propName){
        return $this->_p[$propName];    
    }
    
    public function __set($propName, $propValue){
        $this->_p[$propName] = $value;
    }
}

$a = new TestClass();
$a->testVar = 'test';
print $a->testVar;  //--> 'test'

$a->testVar = new TestClass();
$a->testVar->testVar = 'test2';
// __set of $a->testVar called instead
of getter of $a->testVar and setter of $a->testVar->testVar

Solution Hint:
----------------

I think I is better to call the __get method of the first objects and
than call the __set method of the last one in chain. Like you with __call.
 [2004-11-08 20:05 UTC] php at rodric dot org
I ran into this in a slightly different way -- trying to 
foreach through an ArrayAccess object.  Interestingly, 
removing the __set from class O allows this to work. 
 
 
class O 
{ 
	private $m_a = array(); 
 
	function __get ($key) 
	{ 
		return $this->m_a[$key]; 
	} 
	function __set ($key, $val) 
	{ 
		$this->m_a[$key] = $val; 
	} 
} 
 
class A implements ArrayAccess, IteratorAggregate 
{ 
	private $m_e = array(); 
 
	function __construct ($e = NULL) 
	{ 
		$this->m_e = is_null ($e) ? array() : $e; 
	} 
 
	function offsetSet ($key, $value) 
	{ 
		$this->m_e[$key] = $value; 
	} 
 
	function offsetGet ($key) 
	{ 
		if (isset ($this->m_e[$key])) 
		{ 
			return $this->m_e[$key]; 
		} 
	} 
 
	function offsetUnset ($key) 
	{ 
		unset ($this->m_e[$key]); 
	} 
 
	function offsetExists ($key) 
	{ 
		return isset ($this->m_e[$key]); 
	} 
 
	function getIterator () 
	{ 
		return new ArrayIterator($this->m_e); 
	} 
} 
 
$o = new O(); 
 
$o->a = new A(array(1, 2, 3)); 
 
foreach ($o->a as $e) 
{ 
	echo "$e "; 
}
 [2005-01-11 08:03 UTC] keithm at aoeex dot com
I ran into this problem also, but slighly differently.  In my case, the first property I'm accessing is a validly declared and set property of the class, instead of set using __set or read using __get.  I've tried to reproduce it using simpler code, but I can't seem to get it done.  The actual code I'm using is far too complex for a bug report submission (it's a customized DOM implemtation)

I'll see if I can describe it at all.  Basically I have the following classes that are involved: 
abstract class Node -- Does define __set/__get
abstract class HTMLElement extends Node -- Does define __set/__get
class HTMLInputElement extends HTMLElement -- Does define __set/__get
class FormElement extends HTMLElement -- Does NOT define __set/__get

Now, the FormElement class has variable declarations like so:
        private $minput;
        private $mlabel;
        private $mlplacement;
        private $container;

$minput is eventuall an instance of HTMLInputElement when this error occurs.  Here is the relevent part of the formElement->setInputType function

        public function setInputType($xtype){
                unset($this->minput);
                switch ($xtype){
......
                       case 'radio': case 'text': case 'entry': case 'button': case 'image': case 'submit': case 'reset': case 'checkbox':
                                $this->minput=$this->ownerDocument->createElement('INPUT');
/* as of this point, $this->minput is a valid HTMLInputElement instance, verified w/ print_r */
$this->minput->type = $xtype;
                                break;
...
}


When that method is called, I receive the error in question on the line: $this->minput->type = $xtype;

I'll keep trying to come up with simpler code that reproduces the bug, but if anyone does want to view the big code (maybe for clarity) it is available at http://wiser.kicks-ass.org:8008/PHPDOM/PHPDOM-error.tar.gz


My PHP version is 5.0.2, and I can reproduce with both the CLI and Apache 2 Module.  Operating systems Both FreeBSD and Linux 2.6.9
 [2005-01-12 00:49 UTC] keithm at aoeex dot com
Ok, just wanted to add that I figured my problem out, and you might be able to just consider it user error.  Apparently calling unset on a property of a class makes that property no longer valid for that class, rather than just giving it an empty value and free'ing the memory for it's contents like I had thought.  Not sure if this is intentional or not (does kind of make sense) but anyway removing the unset($this->minput) line from my function and it started working again.
 [2005-01-12 09:08 UTC] derick@php.net
I think this should not happen, Andi, what to you say?
 [2005-01-14 10:16 UTC] derick@php.net
I consulted with Andi and he said:

This is the way the language works. There really isn't anyway to change this behavior. Many rely on the fact that properties can explicitly be created/removed. Of course, I suggest that ppl don't mix between this ability and PPP.

This should be put into the documentation though, I suggest unset().
 [2005-01-18 10:06 UTC] dmitry@php.net
Fixed in CVS (HEAD and PHP_5_0).
 
PHP Copyright © 2001-2014 The PHP Group
All rights reserved.
Last updated: Sun Apr 20 03:02:42 2014 UTC