0

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).

Yupik
  • 4,932
  • 1
  • 12
  • 26
Scentle5S
  • 760
  • 1
  • 6
  • 13
  • 2
    Can you define an interface which MyTrait implements, and then `someMethod` can take an object of that type? – Greg Schmidt Apr 18 '18 at 19:51
  • I'm pretty sure that [Traits can't implement interfaces](https://stackoverflow.com/questions/14665978/why-php-trait-cant-implement-interfaces) and that [you can't type hint a Trait](https://stackoverflow.com/questions/14157586/php-type-hinting-traits). But maybe I didn't understand your comment well and you meant something like like @zzarbi below. – Scentle5S Apr 19 '18 at 02:59

1 Answers1

2

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.

Scentle5S
  • 760
  • 1
  • 6
  • 13
zzarbi
  • 1,832
  • 3
  • 15
  • 29
  • That would do most of what I want, indeed. Thanks for the detailed exemple. However, I believe this won't give me autocompletion on the public variable defined in the Trait, would it ? This might not be that big of a deal, but I think it shows a design problem, since even the IDE isn't able to figure out what's being used. Not only must I go back to the Trait to check the exact syntax of the variable when I want to use it, but if I later want to rename it through refactoring tools, I will run into issues since the IDE won't be able to rename the call in your `foo` method. – Scentle5S Apr 19 '18 at 02:51
  • Correct it won't give you autocompletion on the variable. But does the variable has to be public? – zzarbi Apr 19 '18 at 05:28
  • Well it's a variable that gets computed in another class since it's not really the classes' (`A`, `B` or `C`) job, then set in the instance, and then read from other places. So if I don't set the variable public, I guess I could write accessors, but I don't see the point if all I do is read and write the variable. Maybe there's another design problem here, but I didn't think so and I thought there was a way to achieve what I need while keeping the variable public. – Scentle5S Apr 19 '18 at 06:25
  • 1
    @Scentle5S Then create setter and getter for this attribute and make them part of the interface? – rob006 Apr 19 '18 at 09:36
  • Ok I'll go with that and accept the answer, thanks ! – Scentle5S Apr 19 '18 at 16:08