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 __construct($attributes) {
$attributes['type'] = 'ItemA';
parent::__construct($attributes);
}
public function foo(){
do_abc();
}
}
class ItemB extends Item implements SubItem {
public function __construct($attributes) {
$attributes['type'] = 'ItemB';
parent::__construct($attributes);
}
public function foo(){
do_def();
}
}
class ItemC extends Item implements SubItem {
public function __construct($attributes) {
$attributes['type'] = 'ItemC';
parent::__construct($attributes);
}
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 foo()
function.
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:
Item
class and run different code based on the instance type, which is very messyOne of the answers gave me the idea of overriding the model's methods; I came up with this, which is fairly minimal and works well:
class Item extends Model
{
public function __construct($attributes = [])
{
$attributes['type'] = static::class;
parent::__construct($attributes);
}
public function newFromBuilder($attributes = [], $connection = null): Model
{
$model = parent::newFromBuilder($attributes, $connection);
return class_exists($model->type) && !$model instanceof $model->type
? (new $model->type)->newFromBuilder($model->getAttributes(), $connection)
: $model;
}
}
This allows any number of additional models to be added without having to touch the base class again.