caiosm1005
caiosm1005

Reputation: 1725

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

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

Upvotes: 13

Views: 3885

Answers (4)

Sergey Telshevsky
Sergey Telshevsky

Reputation: 12217

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.

Upvotes: 2

lortabac
lortabac

Reputation: 599

As deceze said, there is no solution to your problem, unless you redesign the class.

If you are using PHP 5.4, you can use Traits.

Create 2 traits, each one with its own Draw function. Then use one trait in the parent and the other in the child.

Then, in order to access the parent function from the child, set an InvokeDraw alias to the parent Draw function.

Here is an example:

<?php

trait DrawingA
{
    function Draw()
    {
        echo 'I am the parent';
    }
}

trait DrawingB
{
    function Draw()
    {
        echo 'I am the child';
    }
}

class ClassA
{
    use DrawingA;
}

class ClassB extends ClassA
{
    use DrawingA, DrawingB {
        DrawingB::Draw insteadof DrawingA;
        DrawingA::Draw as InvokeDraw;
    }
}

$b = new ClassB();

echo 'Child: ',  $b->Draw(), PHP_EOL;
echo 'Parent: ', $b->InvokeDraw() . PHP_EOL;

/*
Outputs:

Child: I am the child
Parent: I am the parent

*/

Upvotes: 2

deceze
deceze

Reputation: 522597

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.

Upvotes: 4

Sergey Telshevsky
Sergey Telshevsky

Reputation: 12217

I liked your question so much I researched a bit and tried to do the same as in my first answer but without static calls:

<?php

class ClassA
{

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

    }

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

class ClassB extends ClassA
{

    function Draw()
    {
        echo "DrawB";
        $x = new parent;
        $x->InvokeDraw();

        /* Drawing code ... */

    }
}

echo "Begin:<br>";

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

Output:

Begin:
DrawBDrawA

Upvotes: 0

Related Questions