3

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.

5
  • Do you mean child class? Ancestor is the (possibly grand)parent.
    – Aaron Hall
    Feb 4, 2016 at 22:31
  • 3
    I think the start of your trouble is conflating hierarchy in a file directory with hierarchy in class inheritance, and now you're trying to overcome that by some awkward contortions. Could you provide (simplified - even pseudo-code) source code for all three classes and a description of how the objects relate to the paths and what you want them to do.
    – HorusKol
    Feb 5, 2016 at 1:09
  • @AaronHall Was tired when I posted this. You are right. I was thinking too much about having method inherited from the ancestor so I messed up.
    – TotoTitus
    Feb 5, 2016 at 9:15
  • 1
    @HorusKol That is indeed what I am doing. I have written an addendum appropriate to the original problem posed, but I have resorted to using a separate class which has the responsibility of traversing the inheritance tree.
    – TotoTitus
    Apr 1, 2016 at 15:28
  • I wanted to note that I found this pattern very common in ReactJS and it explains why a React developer introduced tons of antipatterns into Swift on one of my contracts. I believe we may need a separate thread of Antipatterns ReactJS introduces that new developers should become aware of. Seriously, this question to a reactJS developer would meet an "I don't see why this is a problem"... I wanna pull my hair out. I began with C++ Game Programming, so any violation of SRP causes issues.
    – Stephen J
    Apr 18, 2020 at 18:59

3 Answers 3

1

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.

1
  • Please see the addendum written. Given that calculating a path in this case is inherently related to the class itself, and that I need a child to access the parent's views when it does not have its own, there was no going around not using reflection. I ended up doing a 'WidgetFinder' class, which every Widget has an instance of. The WidgetFinder will go up the inheritance chain of a Widget to find a file.
    – TotoTitus
    Apr 1, 2016 at 15:23
1

why not iterate through the tiers? that should solve your problem without any overhead:

protected function getPath()
{
  $classname = get_class($this);
  do
  {
    $path = BASEPATH. '/'. $classname .'/';
    if (file_exists($path)) { return $path; }
    $classname = get_parent_class($classname);
  } while ($classname);
  return NULL;
}

or, in your code, try { parent::getPath(); } in the else branch. I have not used get_called_class yet, but from the description, that should do the trick, too.

0

I do not speak PHP but it seems to me that this could be solved by having a method that returns the path. Define it in the ancestor, only descendants that have a path on disk implement it. You'll automatically go however far up the chain you need to in order to find someone who implements it.

If said parent doesn't provide what you want but someone higher on the tree does you'll need to get a bit more complex--the implementation will look for the file, if that fails it passes it to it's parent.

Note that in either case I think the logic belongs in the ancestor, the implementations will simply be calling that parent logic with their current location.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.