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

2
  • 2
    Can you define an interface which MyTrait implements, and then someMethod can take an object of that type? Commented Apr 18, 2018 at 19:51
  • I'm pretty sure that Traits can't implement interfaces and that you can't type hint a Trait. But maybe I didn't understand your comment well and you meant something like like @zzarbi below. Commented Apr 19, 2018 at 2:59

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

5 Comments

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.
Correct it won't give you autocompletion on the variable. But does the variable has to be public?
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 Then create setter and getter for this attribute and make them part of the interface?
Ok I'll go with that and accept the answer, thanks !

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.