Model Inheritance in Laravel Model Inheritance in Laravel laravel laravel

Model Inheritance in Laravel


Even though I like the Polymorphic Relations (PR) approach slightly better than the Single Table Inheritance (STI), it still does not feel anything like a true inheritance approach. From the domain (e.g. a UML Class Diagram) point of view, it is like trying to use a composition relationship instead of inheritance.

From a database consistency point of view the polymorphic relation by itself is already a very weird solution in Laravel (IMHO). As there can be no single field that is a foreign key to multiple tables, this can lead to joining ID's that should not be joined. And inverse relationships that are not obeyed.

Although I'm not actually describing a solution to the problem, I'd propose a different approach than PR and STI. This solution would be similar to Hibernate's table-per-subclass approach. I think that Dauce's extension to Eloquent is going in the same direction, except there seem to be a few implementation issues still.

From a database point of view, a table per subclass would also mean that the super-class contains one column per direct subclass. Then you can put foreign key constraints on the (non-null) id's and properly use the relationships. However, there should still be some extra logic in Laravel's magic Eloquent class that turns the requested object into the right type.

So for me, functionally, the Eloquent models should properly inherit on the PHP side, while the database can still use the foreign key constraints.


Note: You can't use an abstract class directly, it has to be extended by a child class

If your Tool (abstract) model doesn't have any table mapped to it then you don't need to use Tool::all and you can't directly use/instantiate an abstract model but you may use an that abstract model as a base class like this:

abstract class Tool extends Eloquent {    protected $validator = null;    protected $errors = null;    protected $rules = array();    // Declare common methods here that    // will be used by both child models, for example:    public static function boot()    {        parent::boot();        static::saving(function($model)        {            if(!$this->isvalid($model->toArray(), $this->rules) return false;        });    }    protected function isvalid($inputs, $rules)    {        // return true/false        $this->validator = Validator::make($inputs, $rules);        if($this->validator->passes()) return true;        else {            $this->errors = $this->validator->errors();            return false;        }    }    public function getErrors()    {        return $this->errors;    }    public function hasErrors()    {        return count($this->errors);    }    // declare any abstract method (method header only)    // that every child model needs to implement individually}class Hammer extends Tool {    protected $table = 'hammers';    protected $fillable = array(...);    protected $rules = array(...); // Declare own rules}class Screwdriver extends Tool {    protected $table = 'screwdrivers';    protected $fillable = array(...);    protected $rules = array(...); // Declare own rules}

Use Hammer and Screwdriver directly but never the Tool model/class because it's an abstract class, for example:

$hammers = Hammer:all();

Or maybe something like this:

$screwdriver = Screwdriver:create(Input::all());if($screwdrivers->hasErrors()) {    return Redirect::back()->withInput()->withErrors($screwdriver->getErrors());}return Redirect::route('Screwdriver.index');


I found a bug (feature?) where Laravel 5 looks up the incorrect class if ParentClass and ChildClass have the same $table string. So if you call ParentClass::all() in certain situations it can return ChildClass instances in the collection!!

The Alpha's answer led me to a workaround where you create the following class structure:

abstract class BaseClass extends BaseModel{    // ParentClass member variables and functions go here, to be shared between parent and child classes}class ParentClass extends BaseClass{    protected $table = 'your_table';    // place no other member variables or functions in this class}class ChildClass extends BaseClass{    protected $table = 'your_table';    // ChildClass member variables and functions go here}

This seems to force Laravel to do the correct lookup when you call ParentClass::all() or ChildClass::all() since their common ancestor doesn't have a table declared. Just treat BaseObject as ParentObject and that way you don't have to muddy your inheritance concept with database details that shouldn't be relevant. I have not stress-tested this thoroughly yet, so be sure to note the reason for this class's existence in your code for future debugging breadcrumbs.