php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #66670 property_exist cannot find private properties defined in superclass
Submitted: 2014-02-08 00:15 UTC Modified: 2014-02-11 23:03 UTC
From: mpope at homeimprovementleads dot com Assigned:
Status: Not a bug Package: Class/Object related
PHP Version: 5.5.9 OS: windows 7
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: mpope at homeimprovementleads dot com
New email:
PHP Version: OS:

 

 [2014-02-08 00:15 UTC] mpope at homeimprovementleads dot com
Description:
------------
Despite reporting as fixed ( https://bugs.php.net/bug.php?id=50810 ) and closed in 5.3.3 according to the change log, a small variation in the logic still produces a bug in 5.5.9.

The fix in 50810 only worked because the method checking the private property was in the same class as the private property.  If you attempt to check the private property of a superclass from the subclass it reports false.  Although I would normally expect this behavior, I think it is counter to the statement found in the docs for property_exists in the change log section: 

"5.3.0 This function checks the existence of a property independent of accessibility."




Test script:
---------------
<?php

function check_scope($ref) {
  echo "vs\n";
  var_dump(property_exists($ref, 'foo'));
  var_dump(property_exists($ref, 'goo'));
  var_dump(property_exists($ref, 'poo'));
  echo "\n";
}

class a {
  private $foo;
  
  public function __construct() {
    var_dump(property_exists($this, 'foo'));
    var_dump(property_exists($this, 'goo'));
    var_dump(property_exists($this, 'poo'));
	
    check_scope($this);
  }
}

class b extends a {
  private $goo;

  public function __construct() {
    var_dump(property_exists($this, 'foo'));
    var_dump(property_exists($this, 'goo'));
    var_dump(property_exists($this, 'poo'));

    check_scope($this);
    parent::__construct();
  }
}

class c extends b {
  private $poo;

  public function __construct() {
    var_dump(property_exists($this, 'foo'));
    var_dump(property_exists($this, 'goo'));
    var_dump(property_exists($this, 'poo'));

    check_scope($this);
    parent::__construct();
  }
}

new c();



Expected result:
----------------
Given the accessibility consideration I would expect property_exists to return true in all of the cases outlined in the test script.  However, running the script will show that property existence checks depend on both the location of the private variable definition AND the location of the property_exists method call.  The results in the check_scope() method do not match the results of the same logic copy and pasted into each constructor method.

Actual result:
--------------
bool(false)
bool(false)
bool(true)
vs
bool(false)
bool(false)
bool(true)

bool(false)
bool(true)
bool(true)
vs
bool(false)
bool(false)
bool(true)

bool(true)
bool(false)
bool(true)
vs
bool(false)
bool(false)
bool(true)

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2014-02-08 00:31 UTC] requinix@php.net
-Status: Open +Status: Not a bug
 [2014-02-08 00:31 UTC] requinix@php.net
It's a matter of inheritance, not accessibility: the subclasses do not inherit the private properties of the superclasses. The properties really don't exist.
 [2014-02-10 23:11 UTC] mpope at homeimprovementleads dot com
Ok, but there is still a bug in that if you call property_exists from the class proper, vs from some externally defined method from the class you get different results. In the example I posted, the check_scope() function should echo the same results as the explicit checks inside the constructors.
 [2014-02-10 23:22 UTC] mpope at homeimprovementleads dot com
Additionally if we're going to be strict to inheritance rules about private properties, we shouldn't see private properties of our subclasses either.  Just the private properties of the currently scoped class.  But in the example you'll notice that property_exist returns true for both the current class's scope AND the 'c' subclass.  Meaning in class a's constructor I can see two private properties, $foo and $poo.  By your statement I should only see $foo.
 [2014-02-11 00:19 UTC] mpope at homeimprovementleads dot com
And this might be worthy of its own ticket, but it looks like all magic methods use an incorrect scope during execution.  I have a __call() method that cannot access a private property declared in the same class when __call() is invoked via a subclassed object.  

In the context of the example script, if class 'a' had a __call() method defined and we called $c->UnknownFunc(), the __call method in class 'a' would not be able to access $foo, even though they're in the same class.  And yet with $c->UnknownFunc() I can access $poo from class a's __call() method even though $poo is defined in class 'c', not class 'a' where the __call method is defined.
 [2014-02-11 01:01 UTC] requinix@php.net
Re: first two replies

I think what you're missing is that even though $foo and $goo are not inherited and don't "exist" in $this, they must clearly exist *somewhere* for A and B to be able to use them.
Here's three simple statements, all restating the same underlying rule:
1. $this->foo exists in an instance of A (1a) and in code inherited from A (1b)
2. $this->goo exists in an instance of B (2a) and in code inherited from B (2b)
3. $this->poo exists in an instance of C (3a) and in code inherited from C (3b)

They fully explain the output:
// C::__construct
bool(false) // $foo: neither 1a nor 1b apply
bool(false) // $goo: neither 2a nor 2b apply
bool(true)  // $poo: both 3a and 3b apply

// B::__construct
bool(false) // $foo: neither 1a nor 1b apply
bool(true)  // $goo: 2a does not apply but 2b does
bool(true)  // $poo: 3a applies (3b does not)

// A::__construct
bool(true)  // $foo: 1a does not apply but 1b does
bool(false) // $goo: neither 2a nor 2b apply
bool(true)  // $poo: 3a applies (3b does not)

// check_scope
bool(false) // $foo: neither 1a nor 1b apply
bool(false) // $goo: neither 2a nor 2b apply
bool(true)  // $poo: 3a applies (3b does not)
 [2014-02-11 01:10 UTC] requinix@php.net
Re: third reply

You're right in that it would belong in a separate ticket, but I don't think your code is quite as you've described.

<?php

class ParentClass {
	private $foo = 123;
	function __call($method, $args) { var_dump($this->$method); }
}

class ChildClass extends ParentClass {
	private $bar = 456;
}

echo "Parent:\n";
$p = new ParentClass();
$p->foo(); $p->bar();

echo "\nChild:\n";
$c = new ChildClass();
$c->foo(); $c->bar();

// Parent:
// int(123)
// NULL

// Child:
// int(123)
// Fatal error: Cannot access private property ChildClass::$bar...

?>
 [2014-02-11 23:03 UTC] mpope at homeimprovementleads dot com
Ok, we're getting close to being on the same page, even if I think that page is a bad design :D. I was incorrect about my third reply that super class private properties were not accessible from super class magic methods triggered by subclasses - they are accessible from inside the class proper, they are just not accessible from a utility function that resides outside the super class even when that super class passes in a reference of $this because $this in the superclass is really just the $this from the subclass (which explains why get_class($this) and get_called_class() return the same value, and why that value is always the subclass in super classes.)  My mistake stemmed from reducing a more complex codebase for the purpose of this ticket, and in that system I was checking property_exists($ref, 'private_var') which would only ever work if I instantiated my base classes directly. Which just seems *crazy* to me because I don't see the purpose in such a design.

So let's see if you concur with my new understanding.  Basically in PHP $this does not refer to the class scope of the keyword's location in the source code, it's only ever a reference to the constructed child-most subclass instance.  And the child-most subclass is granted temporal privileged access to private variables of any super class in its inherency chain when the code execution is physically located inside the respective super class (which screams closure to me, not OO class inheritance.)  

Passing "$this" to an external utility function from a function inside the super class actually passes the sub-classed instance and revokes access to that super class's private variables.  So that means you cannot use a utility library to check base-level private property existence using the $this reference from said base class, you have to check it explicitly inside the base class itself and copy/paste that logic to any other base class that needs it.

If those assumptions are correct I think it's a bug, I think the privileged temporal access to private super class variables should be attached to the $this reference, not just granted based on the line number/file name of where $this is evaluated with respect to the class definition.  Besides the DRY code concerns, there is no way that I know of to externally reflect on the private properties of a live super class instance, which is the whole point of reflection.  If I have to write the logic inside each class explicitly, there's really no need for private variable reflection in PHP.

Thanks for your time/effort/hand-holding on this one, I'm fairly adept with OO concepts from a variety of programming languages, but I'm discovering that PHP deviates from the norm in this manner.
 [2014-02-11 23:52 UTC] php at requinix dot net
Going "off the record", so to speak:

Roughly, yes. I don't know the internal implementation of inheritance, I haven't found anything online talking about it, and I don't want to spelunk through the source looking to understand it, however I would suspect something simpler than a "temporal privilege" system.

As for reflecting the property, it is possible if you go through the parent class for it. Using the ParentClass/ChildClass example I posted,

<?php

$rc = new ReflectionClass($c);
$rp = $rc->getParentClass();
// or $rp = new ReflectionClass("ParentClass") if you know that already

$foo = $rp->getProperty("foo");

// then you can, eg, get the value with
$foo->setAccessible(true); // since $foo is private
echo $foo->getValue($c); // passing an instance of the child

?>
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Mon Apr 29 19:01:30 2024 UTC