tags:

views:

1149

answers:

6

Look at the following code snippet:

<?php

class A {
        function fn() {
                print 'Context: Class:' . get_class($this) 
                        . ' Parent:' . get_parent_class($this) . "\n";
                if(get_parent_class($this)) {
                        parent::fn();
                }
        }
}

class B extends A { }
class C extends B { }

$a = new A();
$c = new C();

$a->fn();

print "\n";

$c->fn();

?>

By running it you will get the following output:

Context: Class:A Parent:

Context: Class:C Parent:B

Fatal error: Cannot access parent:: when current class scope has no parent in /home/andrei/test/test.php on line 10

I believe it should be something like this:

Context: Class:A Parent:

Context: Class:C Parent:B
Context: Class:B Parent:A
Context: Class:A Parent:

What do you think? If get_parent_class($this) returns a not false value should we safely assume parent:: is defined? In what class context is fn() called?

A: 

I am no php wizard but I would guess that the problem is with parent being static

I'm not doing a call to a static method. See: http://www.php.net/keyword.parent
Andrei Savu
A: 

Can't you try to call parent->fn() I've never tried stuff like this before, but that would be my first guess..

MiRAGe
It's not possible to call parent->fn(). See: http://www.php.net/keyword.parent
Andrei Savu
you are right, I was ignorant. My bad.
MiRAGe
+1  A: 

From my own experimentation prompted by your question, it seems that parent:: is resolved as the parent of the class the statement resides in. Alter your test case as follows to see this behaviour:

<?php
class Z {
        function fn() {
                print "This is Z\n";
        }
}

class A extends Z {
        function fn() {
                print 'Context: Class:' . get_class($this)
                        . ' Parent:' . get_parent_class($this) . "\n";
                if(get_parent_class($this)) {
                        parent::fn();
                }
        }
}
class B extends A { }
class C extends B { }
$a = new A();
$c = new C();
$a->fn();
print "\n";
$c->fn();
?>

I agree this is not intuitive and may be a bug.

UPDATE: Interesting what happens when you use this to call the parent instead:

eval(get_parent_class($this). "::fn();");
Chris Thornhill
I believe it's a bug because the function should be called in the context of the child class.
Andrei Savu
It's seems the context is never set as expected.
Andrei Savu
A: 

got this as well

Context: Class:A Parent:

Context: Class:C Parent:B
Fatal error: Cannot access parent:: when current class scope has no parent in PHPDocument3 on line 9

+2  A: 

Calls using parent:: or self:: are considered to be static. These are supposed to be evaluated in the context of the defining class, not in the scope of the calling object. PHP 5.3 adds a new meaning to the word static which would be available for static calls like parent and self, but will be evaluated in the context of the calling class. This is called Late Static Binding. More info on this page:

http://www.php.net/lsb

Edit:

After some thought I believe this behavior is perfectly acceptable. First, let's see why would A want that its foo() method to call the foo() method of its parent, given that A has no parent. Because, it wants the method to always be executed regardless of the implementation of the children. If so, there are other solutions, not that nice though:

class A
{
    final public function __construct()
    {
        echo __METHOD__;

        $this->foo();

        $init = array($this, 'init');
        $args = func_get_args();

        call_user_func_array($init, $args);
    }

    // init would be used as a pseudo-constructor in children
    public function init()
    {}

    final private function foo()
    {
        echo __METHOD__;
    }
}

class B extends A
{}

class C extends B
{}

$c = new C;

If what you tried to do was to execute every foo() method in the chain, then it was expected behavior. If there was a B::foo() function then that would have been executed and if it had contain a call to parent::foo() then A::foo() would have been executed too.

So, probably parent::foo() is somehow confusing. It should be read like (sorry, but I couldn't find a better example):

the_first_parent_in_the_inheritance_chain_which_has_a_foo_method::foo()

That's what you are actually interested in. Why would you want to call foo() in the context of B? The only reason I can think of is for accessing private member from B. But then, how would A know what private members B has? It can't. You cannot use in A::foo() members that A hadn't declared. Unless A is abstract and defines some abstract methods. Of course, A may declare that property as private (B cannot give an overridden member a lower visibility than that in the parent and our aim is that B should have a private one). Then B overrides that property, making it private as we want. Now, if your code would have worked, A or C would have had access to B's private member although it shouldn't. That breaks a rule.

Is there any situation in which you want the behavior you've asked about?

Ionuț G. Stan
Thanks for the quick answer. Strange stuff!
Andrei Savu
@Andrei, I've updated my answer with some further thoughts.
Ionuț G. Stan
I have discovered this behaviour by helping a friend. Now I understand why things are the way they are. Thanks for explanations. I want to chat more with you about php and web apps. Contact me if you want at contact[at]andreisavu[dot]ro. Thanks again.
Andrei Savu
A: 

I think what you are trying to do is wrong. If ur class C has fn() function defined then it would never call fn() in class A, so ther is no meaning in saying parent::fn bcoz ur parent doesnt have function called fn, thts the reason its falling back on fn() defined in A.

But if u want to call some other function in the current object context use $this instead of parent. Herez code for this.

<?php

class A {
        function fn() {
                print 'Context: Function:'.__FUNCTION__.' Class:' . get_class($this)
                        . ' Parent:' . get_parent_class($this) . "\n";
                if(get_parent_class($this)) {
                        $this->fB();
                }
        }
}

class B extends A { }
class C extends B {
        function fB(){
                print 'Context: Function:'.__FUNCTION__.' Class:' . get_class($this)
                        . ' Parent:' . get_parent_class($this) . "\n";
        }
}

$a = new A();
$c = new C();

$a->fn();

print "\n";

$c->fn();

?>

**out put**
Context: Function:fn Class:A Parent:

Context: Function:fn Class:C Parent:B
Context: Function:fB Class:C Parent:B
sudhir