Calling All AncestorsPHP Inheritance Challenge: Calling All Ancestors

PHP’s object-oriented features are exhaustive and modern, yet sometimes life send you a little challenge: who doesn’t like a surprise? Today we’ll look at a rare inheritance puzzle. In this post, we are calling all ancestors in a class hierarchy.

We start with three classes, extending each other in a hierarchy. Each of them has a distinct eponymous method (eponymous is a fancy word for same name). Each version of the method returns a distinct value, for easy identification.

Now, the goal, if you accept it, is to call all three versions of that method, from the deepest child class.

Sounds easy, right? Let’s dive in and see where the trap lies, and how to escape it.

The ‘Calling All Ancestors’ Setup

We define three classes: Grandparents, Parents, which extends Grandparents, and Children, which extends Parents. Each has a non‑static method foo() that returns a unique string.

[pre]class Grandparents
{
    public function foo(): string
    {
        return 'Grandparents';
    }
}

class Parents extends Grandparents
{
    public function foo(): string
    {
        return 'Parents';
    }
}

class Children extends Parents
{
    public function foo(): string
    {
        return 'Children';
    }
}[/pre]

You might note that all the class names are plural: this helps go around a limitation in this illustration, where parent cannot be used as a class name. parent might be used as a method or a constant name, but not a class. It is used as a relative class name by PHP, to access the parent class. And that might be useful later.

Note also that if PHP has its own exception with using parent as a class, British English doesn’t use Childs, but Children. Apparently, exceptions is a common thing in every language, programming or not.

Otherwise, nothing fancy: just classic overriding. Now, our challenge: inside the Children class, we want a method that calls all three foo()implementations and returns an array with the results. Let’s call it callAllFoos().

First Attempts: $this and parent

Inside Children, we can easily call its own foo() using $this:

[pre]public function callAllFoos(): array
{
    $results = [];
    $results[] = $this->foo();      // calls Child::foo()
    // ...
}[/pre]

To call the direct parent’s version, PHP gives us the parent keyword. Pay attention, parent is a PHP keyword, while Parents is the class in the code:

[pre]$results[] = parent::foo();         // calls Parents::foo()[/pre]

That works perfectly. Now we need the grandparent’s version. What about Grandparents::foo()?

[pre]$results[] = grandparent::foo();    // ???[/pre]

First, grandparent is not a PHP keyword, so this is wrong. It would be fun, though, as any prolongement of that allegory would give us greatgrandparent for the 3rd level, and greatgreatgrandparent for the 4th level, and the longest keyword ever for the 128th level (just because 128 levels of inheritance would be a reasonable limit, I guess?)

The Real Challenge: Accessing the Grandparent Properly

We need a way to call Grandparent::foo() on our current object Children.

parent is a good keyword, but it is not possible to chain it to access another level of PHP. PHP does not interpret the right side of the :: call as a keyword, but as a constant name. And, indeed, it is possible to call a constant parent.

[pre]$results[] = parent::parent::foo();    // no such constant as parent[/pre]

The Failed Trick: Closure Binding

A possible plan is to use a closure and bind it with the grandparent’s scope:

[pre]
$grandparentClosure = function() {
    return $this->foo();
};

$bound = $grandparentClosure->bindTo($this, Grandparents::class);
$results[] = $bound();
[/pre]

Unfortunately, this does not work. The scope parameter in bindTo only affects visibility, which make private/protected methods accessible, and static method resolution. For non‑static calls, the method dispatch is still based on the actual class of the object (Child). So $this->foo() inside the closure will still call Children::foo(), not Grandparents::foo().

So we need another approach.

The Successful Trick: Static Method call

[pre]$results[] = Grandparents::foo();   [/pre]

Obviously, this is a static call to a non‑static method. Up to PHP 7, this would trigger a deprecation warning; in PHP 8, it is a fatal error. It is also a bad practice. So technically, this line does not work. And yet…

Just like calling parent::foo() looks like a static call, but is, in fact, dependent on the method’s actual signature, and Grandparents::foo()works here. It is because Grandparents is part of the current class’s hierarchy.

In fact, not only it is possible to call that non-static method statically, but PHP goes the extra mile, and even assign $this, as one would expect in a non-static method. Et voila!

A Reliable Solution: Reflection

Another possible solution is to use the always reliable PHP’s ReflectionMethod:

[pre]public function callAllFoos(): array
{
    $results = [];
    $results[] = $this->foo();          // Child::foo

    $results[] = parent::foo();         // Parent::foo

    $grandparentFoo = new ReflectionMethod(Grandparents::class, 'foo');
    $results[] = $grandparentFoo->invoke($this);  // Grandparents::foo

    return $results;
}[/pre]

ReflectionMethod::invoke($object) calls the method on the given object, using the method’s original definition (from Grandparents). This bypasses any overriding.

Let’s test it:

[pre]$child = new Child();
print_r($child->callAllFoos());[/pre]

Output:

Array
(
    [0] => Children
    [1] => Parents
    [2] => Grandparents
)[/pre]

ReflectionMethod represents the method as defined in a specific class. When we invoke it on an object, it calls that exact method implementation, regardless of whether the object’s class overrides it. The object must be an instance of that class or a subclass, which it is, and the method must be accessible (if it’s private, we’d need setAccessible(true)). In our case, it’s public, so it’s fine.

Conclusion

PHP’s inheritance model gives you direct access to the current class ($this) and the immediate parent (parent), but ancestors beyond that are hidden, unless the methods are not overriden by the lower levels. However, with a static call or with reflection, it is possible to reach any level of the hierarchy. This little puzzle shows that even on a beaten path like OOP, there are still hidden corners and a special way for PHP to save the day.

So, now, it is possible to emulate a ::parentoperator, but what about a ::grandparentoperator, like the ::class one? You might be in luck…