54

I search a way to access to the parent, parent function of a class without to call the parent... Hmmm, that sound a bit weird explanation so i will give an example:

class myclass
{
  public function test() { return 'level 1'; }
}
class myclass2 extends myclass
{
  public function test() { return parent::test() . '-level 2'; }
}
class myclass3 extends myclass2
{
  public function test() { return parent::test() . '-level 3'; }
}
$example = new myclass3();
echo $example->test(); // should display "level 1-level 2-level 3"

I would like to display "level 1-level 3" then doing something like that:

class myclass3 extends myclass2
{
  public function test() { return parent::parent::test() . '-level 3'; }
}

Do you have an idea how I can do this? (I am not allow to edit myclass and myclass2, they are part of a framework...)

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Alexandre
  • 3,088
  • 3
  • 34
  • 53
  • 2
    This question suffers from the [XY problem](http://meta.stackexchange.com/questions/66377/). What's the use-case? Why do you need to do this? There's a decent chance you're breaking the object model in a bad way, one that could leave the object in an inconsistent state (this assumes the parent & grandparent classes themselves are well-designed, and follow proper OO principles, which they very well may not). – outis Nov 21 '11 at 13:04
  • 1
    If your myclass3 should not call myclass2 then you should not extend it from myclass2 but from myclass. Unless you can clarify the UseCase for your question, I'd say your doing it wrong. Inheritance creates a *behaves-as* relationship between the parent and the subclass. In other words myclass3 behaves-as myclass2. By asking myclass3 to behave-as myclass1 you are effectively bypassing the relationship between 3 and 2. So you should not have it in the first place. – Gordon Nov 21 '11 at 13:08
  • To answer to Gordon and the other who ask more details, I need to optimize some part of the magento framework. So there is some rules to follow in the way to extend a class. Also my class3 just overwrite 1 function of 20 function contained in class2. I don't want to copy paste all this function in my class3, doesn't make sense (that's why OOP exist). Then I am oblige to extend from myclass2 and not from myclass. – Alexandre Nov 21 '11 at 14:09
  • @Alexandre: in other words, you are intentionally replacing the behavior of a class by extending it rather than editing the class directly. Fair enough (though potentially dangerous), but would your changes be useful to others? If you're working with the community edition, could you contribute your changes to the Magento project? If so, it would make sense to update the class from the Magento framework instead of extending it. – outis Nov 22 '11 at 03:01
  • @outis: This ecommerce framwork has really big issue of performance and it's known. When they start this project they concentrate on functionalities and not performances. So I got a position to optimize an existing online shop. I don't know if I would be allow to published my code. Also I am not sure that Magento would accept the kind of optimization I am doing. And to finished I can't wait for the update of Magento. After why I don't edit directly the code of the framework. It's just because I want to be able to upgrade and to keep my module with this upgrade. – Alexandre Nov 22 '11 at 08:22
  • @Alexandre: there are source control tools you could use to merge your changes with the main distribution for use on your site until such time as your updates were merged upstream, so updates wouldn't be a problem. You could even start an optimized offshoot of Magento, getting other developers to contribute. – outis Nov 22 '11 at 10:03
  • ... Since Magento is open source, updates to the base may require you to publish any changes you make to the framework (it depends on the license, which I haven't examined), which (if you aren't allowed to publish your changes) may be another reason not to work on the framework itself. However, depending on how you extend the framework, the license may still require that you publish your changes. Moreover, keeping your updates private goes against the spirit of open source. It's thanks to the work of others that you even have Magento, after all. – outis Nov 22 '11 at 10:05

9 Answers9

76

Simple solution. Use the root object myclass directly:

class myclass3 extends myclass2
{
  public function test() { return myclass::test() . '-level 3'; }
}

If you need a more general approach have a look at outis answer.

Community
  • 1
  • 1
PiTheNumber
  • 22,828
  • 17
  • 107
  • 180
  • I also found out the same solution and it's the same concept than get_granparent... But I don't know if it's a good way to do. – Alexandre Nov 21 '11 at 14:09
  • I think using reflection as `get_grandparent_class` did is very nice but for this usecase it might be a bit overloaded. Also it does not work if you have a 4. level myclass4 extends myclass3. – PiTheNumber Nov 21 '11 at 14:16
  • 3
    @Alexandre My guess is you came into this issue when attempting to extend a _prepareCollection() function in a Magento Grid class. I just wanted to concur, that the Magento developers followed poor logic that didn't consider if people would want to extend their functions. They should have included this, at the top of each function: $collection = $this->getCollection(); if (is_null($collection)) { $collection = Mage::getResourceModel('identifier/name'); } – Darren Felton Jul 17 '14 at 20:49
  • 4
    For this to happen: myclass::test(), Shouldn't the method be defined as `static`? – MagePsycho Oct 20 '16 at 14:17
  • 4
    @MagePsycho nope, this is just weird PHP quirkyness. It totally looks like a static method, but it's actually invoking it non-statically. Please never use this pattern since it will lead to confusion when refactoring. – Michael Butler Oct 27 '16 at 20:17
  • And the method must be public as it doesn't work for protected. – MagePsycho Oct 28 '16 at 08:14
  • This is the most elegant answer by far, and it works! Make sure to replicate the call arguments and method parameters (same thing) if your baseMethod accepts any parameters. – Elijah Lynn Jan 24 '20 at 21:24
  • I think this answer could be improved by replacing `test` with `baseMethod`. Could call it parentParentMethod, but I think that is too confusing and base is simple and clear. – Elijah Lynn Jan 24 '20 at 21:25
  • @ElijahLynn If I call it `base()` it would no longer match the question. Also the question is about overwriting a method. We would need to name it `base()` in myclass3 which would make no sense at all. Obviously this code is not a best practice example for useful naming ;) – PiTheNumber Jan 27 '20 at 10:21
25

You could do it using get_parent_class

function get_grandparent_class($thing) {
    if (is_object($thing)) {
        $thing = get_class($thing);
    }
    return get_parent_class(get_parent_class($thing));
}

class myclass3 extends myclass2 {
    public function test() {
      $grandparent = get_grandparent_class($this);
      return $grandparent::test() . '-level 3'; 
    }
}

Or you could use reflection:

function get_grandparent_class($thing) {
    if (is_object($thing)) {
        $thing = get_class($thing);
    }
    $class = new ReflectionClass($thing);
    return $class->getParentClass()->getParentClass()->getName();
}

However, it may not be a good idea, depending on what you're trying to achieve.

jprofitt
  • 10,874
  • 4
  • 36
  • 46
outis
  • 75,655
  • 22
  • 151
  • 221
  • Looks like `get_parent_class` only returns string, which cause `$grandparent::test()` failed to work – VCD Mar 27 '14 at 07:21
  • @VCD: if the code isn't working for you, consider opening a new question. However, first consider whether SO is the appropriate place for your question. The code does work, as class names in PHP *are* strings, so `$class::method()` will invoke `$class`'s `method()` (as long as it's done within a descendent method, or `method()` is static). If you're not getting what you expect, it's likely local to your code, and thus may be off-topic for SO. Try asking in chat to see if you should post a new question, or simply get your question answered there. – outis Mar 27 '14 at 22:39
  • 4
    After tested using the code entirely from this post, the code works. I never know `$class::method` works if `$class` is a string. However I don't agree with you that I should open a new post if the code doesn't work, because it is possible that you made mistake in the post and my comment might help you to correct the mistake. Anyway thanks for the explanation. – VCD Mar 29 '14 at 04:06
  • @VCD: the suggestion to post a question was not intended as a recommendation as something you should automatically do in general if posted code isn't working for you, but specific to this code because I know it does work, for just the reason that you learned. As you mention, posting a comment about non-working code is a good thing to do, though you should first make sure it's because there's an issue with the code itself and not a transcription error. – outis Mar 30 '14 at 05:07
  • 1
    As of PHP 5.5, if you are inside a class, you can get the parent class with `parent::class`. So then you can get the grandparent with `get_parent_class(parent::class)`. – Okonomiyaki3000 May 17 '18 at 07:38
5

Maybe you can just add myclass2 as a member object in myclass3 and try to code like :

class myclass3{
myclass2 obj2;
public function test() { return $obj2->callParentTest() . '-level3';}
}
LostMohican
  • 3,082
  • 7
  • 29
  • 38
  • This can be a solution but in my case that would be a way too complicated. I would have to rewrite the part who is calling myclass3 and I don't this taht's a good idea with the framework I am using (magento) – Alexandre Nov 21 '11 at 14:18
4

No, this is not possible. Unfortunately there is no possibility to refer directly to the original class, only to it's self or to it's parent.

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Wesley van Opdorp
  • 14,888
  • 4
  • 41
  • 59
3

You cannot chain parents, instead create some sort of GetParent() method in your parent classes that simply returns $this;

Eric Herlitz
  • 25,354
  • 27
  • 113
  • 157
  • 1
    `$this` always refers to the instance. It wont magically turn an instance of myclass3 into an instance of myclass2 or myclass1. A call to `$this->getParent()->getParent()->foo()` will still call `foo` from the object context of the myclass3 instance. – Gordon Nov 21 '11 at 13:11
  • I merely suggested that this may be an approach to achieve chaining of classes. However it do require some extra work by instantiating the objects. But you are correct, it does not magically solve this particular problem without the extra work. – Eric Herlitz Nov 21 '11 at 18:38
  • @Trikks: it doesn't solve the problem at all. It's not even part of a solution. – outis Dec 13 '11 at 00:08
2

if you want use the test function directly on class1 you must extend from class1. Please search about polimorphism.

will you try "parent::parent::parent::parent" when you have class5 ??

I think you can add a level parameter to test method. and check it first.

<?php
    class myclass
    {
        public function test($level) 
        { 
            return 'level 1'; 
        }
    }
    class myclass2 extends myclass
    {
        public function test($level) 
        { 

            return $level >= 2 ? parent::test($level) . '-level 2' : parent::test($level); 
        }
    }
    class myclass3 extends myclass2
    {
        public function test() 
        { 
            return parent::test(1) . '-level 3';
        }
    }
    $example = new myclass3();
    echo $example->test(); // should display "level 1-level 3"
1

In some situations, probably you can entirely override the root's method. I mean, instead of calling the parent's parent's one, you can copy the parent's parent's method code and add yours.

Luca Reghellin
  • 7,426
  • 12
  • 73
  • 118
1

my approach: if you have the code:

class A {
    protected function method () {
        var_dump('A -> method');
    }
}

class B extends A {
    protected function method () {
        var_dump('B -> method');
    }
}

class C extends B {
    protected function method () {
        var_dump('C -> method');
    }
}

and you want in class C call method from class A (but not class B) refactor it to the code:

<?php

class A {
    protected function method () {
        $this->methodA();
    }

    protected function methodA () {
        var_dump('A -> method');
    }
}

class B extends A {
    protected function method () {
        var_dump('B -> method');
    }
}

class C extends B {
    protected function method () {
        $this->methodA();
    }
}
Marek Woźniak
  • 1,766
  • 16
  • 34
1

There is no operator to get the root object. I would do something like this:

class myclass
{
  public function getRoot() { return __CLASS__; }
  public function test() { return 'level 1'; }
}
class myclass2 extends myclass
{
  public function getRoot() { return parent::getRoot(); }
}
class myclass3 extends myclass2
{
  public function getRoot() { return parent::getRoot(); }
  public function test() {
    $grandparent = self::getRoot();
    return $grandparent::test() . '-level 3';
  }
}
$example = new myclass3();
echo $example->test(); // should display "level 1-level 2-level 3"
outis
  • 75,655
  • 22
  • 151
  • 221
PiTheNumber
  • 22,828
  • 17
  • 107
  • 180
  • the `self` keyword only works when used with scope resolution (`::`)–it's not a value and can't be returned from a function. There were a couple of other problems with `test()` as well. – outis Nov 21 '11 at 13:13
  • Some problems have been fixed by outis, but this is still not working. Even if I make test() static. I think `myclass::test()` is better. – PiTheNumber Nov 21 '11 at 13:45