Search code examples
phpstructuremultiple-inheritancecomposition

Solving multiple inheritance in PHP without composition


I'm working on a web app with Yii2 and PHP and am facing a typical multiple inheritance situation.

I have the classes A and B extend Yii2's ActiveRecord class since they represent relational data stored in a DB. But then I have class C, which doesn't (and shouldn't) extend ActiveRecord but shares common behaviors and member variables with A and B. In other words, the three classes genuinely share a common "is a" relation with a real-world entity, but only A and B are storable in a DB.

The way I've got things somewhat working until now is by using traits :

abstract class AbstractMotherClass extends ActiveRecord {
    use MyTrait;
}

class A extends AbstractMotherClass {}

class B extends AbstractMotherClass {}

class C {
    use MyTrait;
}

trait MyTrait {
    public $someVariableInherentToAllThreeClasses;

    public function someMethodInherentToAllThreeClasses() {
        // do something
    }
}

Then I have a method which can take any of the three classes (A, B or C) and work with it. Until now, I only had to throw A or B at it so I just wrote

public fonction someMethod(AbstractMotherClass $entity) {}

so I could get type hinting and other things in my IDE. But now I have to pass C as well and the app crashes since the method doesn't get its expected AbstractMotherClass instance if I call someMethod(new C());. To solve this, I would need a common class that all A, B, AND C could extend, so that I could type hint that class in my method. But that would be multiple inheritance since A and B must also extend ActiveRecord but C can't.

I've found a lot of multiple inheritance problems, but they all have been solved by changing the object structure, splitting responsibilities, using composition over inheritance, and so on. I couldn't manage to apply those solutions here as they didn't seem suitable nor practical, but I might be wrong.

What would be the best way to do this ?

Also if anyone has a better title suggestion, I'd be happy to change it (I couldn't find a good one).


Solution

  • As Greg Schmidt mentioned as well, you could use interfaces

    class ActiveRecord {
    
    }
    
    interface SameInterface {
        public function someMethodInherentToAllThreeClasses();
    }
    
    abstract class AbstractMotherClass extends ActiveRecord implements SameInterface{
        use MyTrait;
    }
    
    class A extends AbstractMotherClass {}
    
    class B extends AbstractMotherClass {}
    
    class C implements SameInterface{
        use MyTrait;
    }
    
    trait MyTrait {
        public $someVariableInherentToAllThreeClasses;
    
        public function someMethodInherentToAllThreeClasses() {
            return 'bar';
        }
    }
    
    function foo(SameInterface $o) {
        return $o->someMethodInherentToAllThreeClasses().PHP_EOL;
    }
    
    echo foo(new C());
    

    Granted you have to copy paste someMethodInherentToAllThreeClasses in the interface. Interfaces are usually used for solving some multiple inheritance problem.