I’ve got a requirement for a set of models with the same database table, but they need to implement certain methods in a different way. Given a set of classes like this:
class Item {
protected $table = 'items';
}
interface SubItem {
public function foo();
}
class ItemA extends Item implements SubItem {
public function foo(){
do_abc();
}
}
class ItemB extends Item implements SubItem {
public function foo(){
do_def();
}
}
class ItemC extends Item implements SubItem {
public function foo(){
do_ghi();
}
}
class Owner {
public function items() {
return $this->hasMany(Item::class);
}
}
All works fine until I try to fetch the items()
relationship. The resulting instances are cast to the Item
class, and I’m unable to call the function. There is a type
column which indicates what the model is.
It seems like I should be able to declare Item
as abstract and use the type column to have the relationship return a collection of ItemA
, ItemB
, and ItemC
instances but this is clearly not the case. I may want to add an arbitrary number of additional child classes in the future. Does Laravel have a way to make this work?
Alternatives considered:
- Polymorphic relationships, which don’t seem to address this situation
- Add the method to the
Item
class and run different code based on the instance type, which is very messy - Add relationships for every type and have a ‘fake’ relationship method collect them all behind the scenes, which is slightly less messy but doesn’t seem possible
2
Answers
If you have a column indicating the type of model in it should be, try using the
newFromBuilder
method to create a new instance of theItem
as the correct type. I am not sure which field specifies the model type in your case, but I will assume it istype
:i would try this quickly:
and then the client would call it like this:
and now the correct foo method could be called on it, as an alternative as mentioned in the comment of the original post the state pattern should do it.