Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access parent's overridden method from parent's context in PHP

Tags:

php

class

I have a drawing PHP class called ClassA that is extended by many other drawing classes, ClassB for instance.

I need the inherited classes to fire its parent classes' Draw() method. However, in my particular situation, I do not want to call such method directly (e.g.: parent::Draw()). I'd like a third function (e.g.: parent::InvokeDraw()) to call my drawing method from within the parent's context.

Here's some code to illustrate:

class ClassA
{
    function Draw()
    {

        /* Drawing code ... */

    }

    function InvokeDraw()
    {
        $this->Draw();
    }
}

class ClassB extends ClassA
{
    function Draw()
    {
        parent::InvokeDraw();

        /* Drawing code ... */

    }
}

The problem I'm facing is that InvokeDraw() will not call the parent's Draw() method, but rather the extended class' own Draw() method, thus causing an infinite loop.

Although the issue is fairly logical, I am having a hard time figuring out a workaround on that. How to accomplish this task?

Desired effect

chart

Infinite loop problem

chart

like image 560
caiosm1005 Avatar asked Dec 14 '12 08:12

caiosm1005


2 Answers

On a superficial level, inheritance works like merging code. Inherited methods are part of the child class as if they were native to the class:

class A {

    public function foo() {
        echo 'foo';
    }

    public function bar() {
        echo 'bar';
    }

}

class B extends A { }

For all intents and purposes, this is equivalent to writing:

class B {

    public function foo() {
        echo 'foo';
    }

    public function bar() {
        echo 'bar';
    }

}

Class B has the function foo and bar as if they were part of its declaration.

Overriding methods in a child class replaces the one specific implementation with another:

class B extends A {

    public function bar() {
        echo 'baz';
    }

}

This is as if B was declared like this:

class B {

    public function foo() {
        echo 'foo';
    }

    public function bar() {
        echo 'baz';
    }

}

With one exception: the parent's implementation of a method is available using the parent keyword. It does not change the context in which the code is executed, is merely uses the parent's implementation of the method:

class B extends A {

    public function bar() {        public function bar() {
        parent::bar();      --->       echo 'bar';
    }                              }

}

It works in the same context of the current instance of B, it just pulls the old code of the parent out of the ether.

If you call another method in the parent's method, it executes in the context of the child, not in the context of the parent class. Because you have not instantiated the parent, you are working with a child. To demonstrate with properties (it works the same with methods):

class A {

    protected $value = 'bar';

    public function bar() {
        echo $this->value;
    }

}

class B extends A {

    protected $value = 'baz';

    public function bar() {
        parent::bar();     // outputs "baz"
        echo $this->value; // outputs "baz"
    }

}

As such, there is no solution to your problem. You cannot call a method in the "parent's context". You can call code of the parent in the current context, that's all. What you're creating is a simple loop, because it doesn't matter whether the code is inherited or not, it all works in the same context.

You need to redesign that class to not call methods in a loop.

like image 100
deceze Avatar answered Sep 26 '22 00:09

deceze


This one is with using static methods

<?php

class ClassA
{
    function Draw()
    {
        echo "DrawA";
        /* Drawing code ... */

    }

    function InvokeDraw()
    {
        self::Draw();
    }
}

class ClassB extends ClassA
{
    function Draw()
    {
        echo "DrawB";
        parent::InvokeDraw();

        /* Drawing code ... */

    }
}

echo "Begin:<br>";

$cb = new ClassB();
$cb->Draw();

Note that the only thing I changed is the InvokeDraw() method and made it use self which refers to a class, rather than object as it is with $this

Output:

Begin:
DrawBDrawA

Edit: To answer your comment below I will add a short description of how your code works and how this code works.

What happens in your code:

  1. We create B and start working with it
  2. We call B->Draw() while working within B class AND B object.
  3. B->Draw() calls statically(that means class method) A::Invoke() from class A BUT we are still using B object.
  4. Static call A::Invoke() calls $this->Draw(); and as we are working currently with B object, $this refers to an instance of ClassB.
  5. And here we go looping.

What happens in the code above:

  1. We create B and start working with it
  2. We call B->Draw() while working within B class AND B object.
  3. B->Draw() calls statically(that means class method) A::Invoke from class A BUT as well as in your code we are still using B object.
  4. Static call A::Invoke() calls self::Draw() which is basically the same as ClassA::Draw() and because it's a static method, we don't care what object we are currently working with and we call the A's Draw() method.
  5. A::Draw() method executes as we need.

I will provide the same explanation for the code in my second answer, which doesn't use static calls:

  1. We create B and start working with it
  2. We call B->Draw() while working within B class AND B object.
  3. B->Draw() CREATES an instance of A.
  4. B->Draw() calls A->Invoke() which means we start to work with an object that is instance of class A and not B like before.

At this point we completely forget that B even exists and work only with A

  1. A->Invoke() calls $this->Draw() which means that we are calling A->Draw(), because we already work with an instance of class A.
  2. A->Draw() executes as we expect.

From usability point of view, we can see that the static method is better, as we can define some static properties and you can work with them when A::Draw() executes. If we use non-static method, then we need to pass the data we need within arguments of our methods.

I hope this makes it clear. The sequences above do not have the right terminology, but it was written on purpose, I think it's easier to understand the flow that way.

like image 25
Sergey Telshevsky Avatar answered Sep 23 '22 00:09

Sergey Telshevsky