Search code examples
phpoopparent

In PHP, how do I call a method top-down recursively through the hierarchy from a method in the grandest parent?


I have a class that is extended, and its children extended further, an arbitrary number of times. Each extension provides more functionality than its predecessor. The catch is, not all parameters can be provided at initialization time. It is complex enough to warrant passing more configuration options via methods, and then calling some sort of build() or configure() method to ready itself for operation.

The catch is, each class in the hierarchy needs a chance to configure itself, and it needs to cascade from the parent to all of the children.

I successfully do this below, but this solution requires that each class remember to call it's parent's method, otherwise it breaks. I want to remove that responsibility from those who might forget to do such.

How do I modify this code to achieve this?

<?php

class A {
    function configure() {
        print("I am A::configure() and provide basic functionality.<br/>\n");;
    }
}

class B extends A {
    function configure() {
        parent::configure();
        print("I am B::configure() and provide additional functionality.<br/>\n");
    }
}

class C extends B {
    function configure() {
        parent::configure();
        print("I am C::configure() and provide even more functionality.<br/>\n");
    }
}

$c = new C;
$c->configure();

?>

Output:

I am A::configure() and provide basic functionality.
I am B::configure() and provide additional functionality.
I am C::configure() and provide even more functionality.

Thanks for your help! :)


Solution

  • Without claiming it's pretty, I'd suggest the following. I left out your configure() functions for brevity.

    <?php
    
    class A {
      function configure_master() {
        $init_class = get_class($this);
        $ancestry = array();
        while (! empty($init_class)) {
          $ancestry[] = $init_class;
          $init_class = get_parent_class($init_class);
        }
        for ($i = count($ancestry) - 1; $i >= 0; $i--) {
          call_user_func(array($this, $ancestry[$i] . '::configure'));
        }
      }
    }
    
    $c = new C;
    $c->configure_master();
    

    I tested this, and it works. call_user_func($ancestry[$i] . '::configure') also works (at least in php 5.3, which is where I tested it), but it relies on the odd (to me) scoping of $this. I'm uncertain whether this is reliable or will carry forward into future versions. The version above "should be" more robust, given my limited knowledge.

    If keeping the outside call as configure() is a requirement, I'd suggest renaming all your "configure()" methods to "configure_local()" or some such, and rename "configure_master()" to "configure()". In fact, that's what I'd do to start with, but that's a matter of personal taste.

    If you need both the internal method and the external API to remain the same... Then you're stuck, since the base class method is overridden, but I'm sure you knew that.