Object-oriented – Child class accessing its parent’s method from Ancestor method

inheritanceobject-orientedPHPreflection

I find myself right now banging my head with the following issue (in PHP):

I have an abstract base class, which has a non-abstract method, inherited and unchanged all over the inheritance chain (which is 3-tiered right now, only the first tier is abstract).

This non-abstract method contains logic to calculate a disk path, which is done by taking into account the class' name. Both children and grandchildren adopt this method/algorithm with no modifications.

However, I desire to implement functionality so that if a Grandchild does not have its "own" specific path on the disk, it will attempt to use its parent's path. Only the parent can calculate this path (only the parent knows its own name) – I don't want to use reflection to store the parent's name into a variable, and I would also like this to work homogeneously – if the inheritance chain is longer, it should try to go up the inheritance chain to ask any of its ancestors for a "place" (the aforementioned path).

A parent::Method() call in PHP (as in other OOP languages) will try to call the parent of the current class, not the parent of the class of the object at run-time.

In PHP, one can do this through Reflection, but I am more or less persuaded that this breaks some principle of OOP which I am not aware of.

//Abstract class BaseSomething...
if(!$this->Calculation()) {
  $parentClass = (new \ReflectionObject($this))->getParentClass();
  if(!$parentClass->isAbstract()) {
    $method = $parentClass->getMethod('Calculation');
    $method->setAccessible(true);
    $this->result= $method->invoke($this, $arg ...);
  }
}

This works, does the job. It allows for a pseudo-recursion of sorts, stopping at the first ancestor who can provide us with what we need, but something must be more or less wrong along the way.

A possible solution whereof I am thinking is having a constructor chain, in which each class adds its name to an array. This would imply forcing each child to call the parent's constructor and to also add its name to the array, which is also not pretty.

Maybe the issue lies in what I am trying to do – using inheritance in a way more encompassing than what it was ever intended to do.

I would like to hear some thoughts on how this can be refactored in such a way as to not break rules, or perhaps some would consider than given my use case, using such foundation-redefining code is "permissible".

Edit: I apologize for letting this question drift away for more than a month and shirking my responsibility of adding examples and answering questions. It was, partly, just base slothfulness.

Continuing in the same vein as the original post, this is how the three tiers roughly look like (simplified as much as possible, almost identical to above):

abstract class BaseWidget extends \Illuminate\Routing\Controller {
    private function getPath($file) {
         $name = return get_called_class(); //Uses get_class_name
         $file = $some_base_path . '/' . $name . '/';
         if(file_exists($file)) {
             return $file;
         } else {
             $refl = new \ReflectionObject($this);
             $parentRefl = $refl->getParentClass();
             $methodRefl = $parentRefl->getMethod('getPath');
             $methodRefl->setAccessible(true);
             $path = $methodRefl->invoke($this);
             return $path;
         }
    }   
}

class Widget extends BaseWidget {
     //This guy resides in App/Widgets/Widget/Widget.php,
     //and has a view in Widget/Views/View.blade.php
     //When $widget->getPath('Views/View.blade.php') is called,
     //it will know how to 'find itself' and where to look for its own
     //view         
}

class SpecialisedWidget extends Widget {
     //This guy resides in ...Widgets\SpecialisedWidget.php
     //Its 'views' folder is empty, so when it has to find it,
     //it will instead borrow it from its parent. If this class
     //also had a child with no view, it would go up to Widget on the
     //inheritance chain.
}

I recognise that I am using inheritance to do something which inheritance is not supposed to do. However, the functionality which I had to implement – A Widget borrowing views from its parent, which are relative to the parent's location on disk – whereof the child is unaware – necessitated that I cheat just a little bit.

Best Answer

Without knowing too many details about the responsibilities of this class hierarchy, it sounds like you may be violating SRP.

I would consider using constructor injection to pass in an object that can calculate a path. This is an application of the strategy pattern.

This way you decouple the responsibilities of the class from the responsibility of knowing what path to work with: the object just knows it can call a method on some other object and get a directory path to use.

Related Topic