php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #19376 Class inheritance problem
Submitted: 2002-09-12 13:05 UTC Modified: 2002-12-06 19:34 UTC
Votes:5
Avg. Score:3.8 ± 1.0
Reproduced:2 of 2 (100.0%)
Same Version:1 (50.0%)
Same OS:1 (50.0%)
From: shamim at poetryunlimited dot com Assigned:
Status: Wont fix Package: Scripting Engine problem
PHP Version: 4.2.2 OS: Linux/Windows NT 4
Private report: No CVE-ID: None
View Add Comment Developer Edit
Anyone can comment on a bug. Have a simpler test case? Does it work for you on a different platform? Let us know!
Just going to say 'Me too!'? Don't clutter the database with that please — but make sure to vote on the bug!
Your email address:
MUST BE VALID
Solve the problem:
6 + 19 = ?
Subscribe to this entry?

 
 [2002-09-12 13:05 UTC] shamim at poetryunlimited dot com
Should I be able to do the following? 

In the example below, class test1 is a base class for test2 which is a base class for test3. 

In an object of type test1, if I call the class method test3::showMe, I would expect the class method test3::showMe would be called. This class method (not member function) would would have no class context ($this), since objects of type test1 do not have a member function test3::showMe. 

This call should therefore fail at the debug call.

Instead, the call traverses up to test3, and the recursive parent::showMe calls reverse the traversal back to the class test1, even though class test1 has no knowledge of class test3 or class test2. Or so it should be. And for some reason the $this variable exists at every level. Even though objects of type test1 do not have any test3 or test2 object contexts.

I think the polymorphism is broken. Thanks.

Code follows.
===========================================================
<?php
Header("Content-Control: no-cache");
Header("Pragma: no-cache");
function debug(&$obj,$msg)
{
  echo "$msg<br><pre>";var_dump($obj);echo "</pre><hr>\n";
}
class test1
{
  var $a;
  function test1()         {$this->a=0;debug($this,"test1->new");}
  function setA()          {$this->a+=1;debug($this,"test1->setA");}
  function display()       {debug($this,"test1->display");echo $this->a."<br>\n";}
  function callMe()        {debug($this,"test1->callMe");test3::showMe();}
  function showMe()        {debug($this,"test1->showMe");$this->display();}
}

class test2 extends test1
{
  function test2()         {debug($this,"test2->new");$p=get_parent_class($this);parent::$p();}
  function display()       {debug($this,"test2->display");parent::display();}
  function showMe()        {debug($this,"test2->showMe");parent::showMe();}
}
class test3 extends test2
{
  function test3()         {debug($this,"test3->new");$p=get_parent_class($this);parent::$p();}
  function display()       {debug($this,"test3->display");parent::display();}
  function showMe()        {debug($this,"test3->showMe");parent::showMe();}
}
$b=new test2();
$b->setA();
$c=new test1();
$c->setA();
$a=new test1();
$a->showMe();
$a->callMe();
$a->setA();
$a->showMe();
$a->callMe();
$a->setA();
$a->showMe();
$a->callMe();
$a=new test2;
$a->showMe();
$a->callMe();
$a->setA();
$a->showMe();
$a->callMe();
$a->setA();
$a->showMe();
$a->callMe();

/*

Results:

test2->new

object(test2)(1) {
  ["a"]=>
  NULL
}

--------------------------------------------------------------------------------
test1->new

object(test2)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
test1->setA

object(test2)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
test1->new

object(test1)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
test1->setA

object(test1)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
test1->new

object(test1)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
test1->showMe

object(test1)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
test1->display

object(test1)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
0
test1->callMe

object(test1)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
test3->showMe

object(test1)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
test2->showMe

object(test1)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
test1->showMe

object(test1)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
test1->display

object(test1)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
0
test1->setA

object(test1)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
test1->showMe

object(test1)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
test1->display

object(test1)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
1
test1->callMe

object(test1)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
test3->showMe

object(test1)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
test2->showMe

object(test1)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
test1->showMe

object(test1)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
test1->display

object(test1)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
1
test1->setA

object(test1)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
test1->showMe

object(test1)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
test1->display

object(test1)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
2
test1->callMe

object(test1)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
test3->showMe

object(test1)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
test2->showMe

object(test1)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
test1->showMe

object(test1)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
test1->display

object(test1)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
2
test2->new

object(test2)(1) {
  ["a"]=>
  NULL
}

--------------------------------------------------------------------------------
test1->new

object(test2)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
test2->showMe

object(test2)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
test1->showMe

object(test2)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
test2->display

object(test2)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
test1->display

object(test2)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
0
test1->callMe

object(test2)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
test3->showMe

object(test2)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
test2->showMe

object(test2)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
test1->showMe

object(test2)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
test2->display

object(test2)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
test1->display

object(test2)(1) {
  ["a"]=>
  int(0)
}

--------------------------------------------------------------------------------
0
test1->setA

object(test2)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
test2->showMe

object(test2)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
test1->showMe

object(test2)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
test2->display

object(test2)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
test1->display

object(test2)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
1
test1->callMe

object(test2)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
test3->showMe

object(test2)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
test2->showMe

object(test2)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
test1->showMe

object(test2)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
test2->display

object(test2)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
test1->display

object(test2)(1) {
  ["a"]=>
  int(1)
}

--------------------------------------------------------------------------------
1
test1->setA

object(test2)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
test2->showMe

object(test2)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
test1->showMe

object(test2)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
test2->display

object(test2)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
test1->display

object(test2)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
2
test1->callMe

object(test2)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
test3->showMe

object(test2)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
test2->showMe

object(test2)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
test1->showMe

object(test2)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
test2->display

object(test2)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
test1->display

object(test2)(1) {
  ["a"]=>
  int(2)
}

--------------------------------------------------------------------------------
*/
?>

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2002-09-12 13:07 UTC] shamim at poetryunlimited dot com
when an object of type test3 calls test3::showMe is the only time this chain should function as displayed, I believe.

Thanks.
 [2002-09-12 18:15 UTC] alan_k@php.net
Sorry, but the bug system is not the appropriate forum for asking
support questions. Your problem does not imply a bug in PHP itself.
For a list of more appropriate places to ask for help using PHP,
please visit http://www.php.net/support.php

Thank you for your interest in PHP.

I cant see any unexpected behaviour here, - however it would help *alot* if you made a shorter example... 
 [2002-09-12 23:11 UTC] shamim at poetryunlimited dot com
Thank you for explaining to me that I did not explain my problem clearly.

My question was rhetorical. Having been a systems engineer for the past 15 years, I know that this is an error. I was merely trying to be polite and draw your attention to it.

I do not appreciate the unwarranted brush off, nor do I appreciate being told that this was a support request - I don't want to know how to do something. I want something fixed. Thanks.

Hopefully you can correct this or alert Zeev that there is a problem in calling static methods from within a class instance when that static method contains the original class as part of its hierarchy.

It allows the $this context to be passed from an object to a static method and back, violating class scoping. Please see the other bug report for a more clearer example.

Shamim Islam
BA BSc

P.S. I will be posting this as many places as I can find a forum until someone understands that class scoping is being violated willy nilly.
 [2002-09-12 23:25 UTC] alan_k@php.net
apologies for the brush of effect. 

if you could mark the first instance where you think the behaviour is unexpected it would help. I assume you realize that all static method calls take with them the '$this' value of the calling objects (always). 

It's a feature, and It has a few usefull side effects, like making a sudo multiple inheritance possible...

 [2002-09-12 23:33 UTC] shamim at poetryunlimited dot com
Please see bug report http://bugs.php.net/bug.php?id=19384 for further details.

You say that static method calls take $this with them? But how can they take them OUT OF the current class? Which $this is in effect in the class of the static method?? What happens if the static method call has it's own variable of the same name or function?

I did not see anywhere in the documentation that states that static method calls take $this with them, even out of the current class.

I'm confused. How can an object of type A call a static method of class B and have $this taken with it???

This violates class scoping as far as I understand it.

I don't see how this would enable multiple inheritance, especially since PHP is supposed to be single inheritance anyway.

You're saying this isn't a bug? Isn't that like someone telling me a year ago that &new was necessary and it wouldn't be fixed because it was a feature, and now Zend 2.0 will do it by default???

Suggestions? Where should I take this issue? And where do I find the documentation that clearly describes the expected behavior. 

Thanks.
 [2002-09-12 23:45 UTC] alan_k@php.net
dupe of http://bugs.php.net/bug.php?id=12622

I'm not sure if it will be fixed.. nobody has commented on 12622

It may be worth posting a question on php-dev, just to get a more definitive answer...


 [2002-09-12 23:50 UTC] alan at akbkhome dot com
oops forgot to mention - its documented
http://www.php.net/manual/en/keyword.paamayim-nekudotayim.php 

"In this context, there is a current object and it may have object variables. Thus, when used from WITHIN an object function, you may use $this and object variables."
 [2002-09-13 00:04 UTC] shamim at poetryunlimited dot com
This is not the same as the one in the documentation that you sent me.

In the documentation, you are calling A::example directly, or calling B::example which calls up the chain to A.

In that context, it's fine. Objects of type B contain an A context. 

It's when you call B::example from within A::example that all hell breaks loose. Objects of type A have no B context.

Example.
<?php
error_reporting(E_ALL);
class A
{
  var $a;
  // This should be a static call - the only way it makes sense.
  function example(){echo $this->a;B::Example();}
}

class B extends A
{
  var $b;
  // This should fail when called from objects of type A.
  // And it does, but only because b is undefined, not
  // because $this is undefined. But since an object of 
  // type A called this static method, $this should not
  // exist. The error should be undefined variable $this
  // not undefined property b.
  function example(){echo $this->b;}
}
$a=new A;
$a->example();
?>
---------------

My 3 class script below also illustrates that not only is the class context passed out of the current object to a static method, when the function call chain returns to the original class, which should have no context, the original context is still carried.

I really don't see how this is documented. Is there any further documentation? You're suggesting I subscribe to php-dev??

Duplicated below w/o the results.

---------------

<?php
// Example of bug in PHP class method passing $this incorrectly

class test1
{
var $a;
function test1()
function showMe()
{
// Since test3::showMe was called as a static method
// This too should be a static method call with no $this
echo 'In test1::showMe<hr>';
echo 'Next 2 lines should fail since this method was not called
from within this object<br>';
echo '$this is of type '. get_class($this)." in
test3::showMe<br>\n";
echo "test1::showMe:a=".$this->a."<br>\n";
}
// Class member to test static method call
function callMe()
{
echo 'In test1::callMe<hr>';
echo '$this is of type '.get_class($this)."in
test1::callMe<hr>\n";
echo 'Calling class method test3::showMe from an object of type
test1<br>';
echo '$this should not be passed since test3::showMe does not exist
in objects of type test1<hr>';
// This is a static method call, since test1 objects
// do not have a test3::showMe.
test3::showMe();
}
}

class test2 extends test1
{
function showMe()
{
echo 'In test2::showMe<br>';
echo "Next 2 lines should fail since objects of type test1 cannot
pass \$this<hr>\n";
echo '$this is of type '. get_class($this)." in
test2::showMe<br>\n";
echo "test2::showMe:a=".$this->a."<hr>\n";
echo 'Calling parent::showMe<hr>';
// Since test3::showMe was called as a static method
// This too should be a static method call with no $this
parent::showMe();
}
}

class test3 extends test2
{
function showMe()
{
echo 'In test3::showMe<br>';
echo "Next 2 lines should fail since objects of type test1 cannot
pass \$this<hr>\n";
echo '$this is of type '. get_class($this)." in
test3::showMe<br>\n";
echo "test3::showMe:a=".$this->a."<hr>\n";
echo 'Calling parent::showMe<hr>';
// Since test3::showMe was called as a static method
// This too should be a static method call with no $this
parent::showMe();
}
}

// object $a is of type test1
$a=new test1;
$a->callMe();
?>
 [2002-09-14 04:17 UTC] derick@php.net
This is just how it is implemented, and it can now not be changed because of backward compatibility -> won't fix.

Derick
 [2002-09-17 21:09 UTC] shamim at poetryunlimited dot com
Ok. I'm really having a serious issue with this.

I am not asking to fix anything that should eliminate backwards comaptibility, because in any forum you go to, this behavior is wrong, when considering object-oriented programming.

All I'm saying is that instead of scanning the class hierarchy from the object class type, in either direction, that the class hierarchy be scanned towards the base class only from the object class type.

Each variable carries with it, it's type.

You cannot seriously tell me this is difficult to implement, and obviously since you shouldn't be scanning down a hierarchy from the current object's class type anyway, you eliminate needing to spend extra time so the whole process should be faster anyway.

Could you please give me one instance where backwards compability would be sacrificed?

Having spent a good chunk of my education in studying compilers and optimization and operating systems, I am really having trouble with the overall attitude I'm getting about a problem that in my eyes is a win-win if it's fixed, since we would have a proper object oriented environment where code would not break just because someone was sloppy in setting up their classes.

Come on. This is really serious stuff.

Because what you're essentially saying is that if 2 class hierarchies are derived from PEAR, and that one of the instances in that hierarchy calls a PEAR function and that PEAR function somehow needs to call something in the other class hierarchy by some derived module, that that SECONDARy static call to the alternative class hierarchy from the same root, would then become a CLASS METHOD call making the situation untenable and the problem unsolvable.

Is it just resources? Do you want me to look at the code and find out what would need to be done?

All I've been hearing is "We can't do it, it isn't do-able, there's too much that will break".

If any of these things were true, PHP would not be the success it is today.

So what do you want to do??? I'm game for some collaboration.

I'm not asking for a silver bullet.

I've ante'd up. Anyone going to call???

Thanks.
 [2002-09-19 02:12 UTC] derick@php.net
One more time, and the last time: This is expected behaviour. It might be wrong, but it is not a bug. Feel free to write a patch, suspending until you send one.

Derick
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Mar 28 11:01:27 2024 UTC