Model Inheritance in Laravel

23,407

Solution 1

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.

Solution 2

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');

Solution 3

The only ORM in PHP that I know that does a one table per class hierarchy is Doctrine. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html. And I believe that it can map this kind of hierarchy you're planning to use, an abstract superclass that can access all subclasses.

To integrate Doctrine to Laravel, I suggest to use laravel-doctrine package which you have all needed info in this website http://www.laraveldoctrine.org/.

Solution 4

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.

Share:
23,407

Related videos on Youtube

Gabriel Matusevich
Author by

Gabriel Matusevich

Updated on July 18, 2022

Comments

  • Gabriel Matusevich
    Gabriel Matusevich almost 2 years

    i'm currently working with Laravel and I'm struggling with the fact that every model needs to extend from Eloquent and I'm not sure how to implement a model Hierarchy (with different tables)

    For Example:

    Let's say I have an abstract model Tool, and then a Model Hammer and a Model Screwdriver that extend from Tool.

    Now, Tool would extend Eloquent ... BUT, there is NO Table for Tools, there is a table for Hammers and another Table for Screwdrivers, because they have different attributes.

    How do I specify that Hammer has a table and Screwdriver has a table when they both extend Tool? And how can I use Eloquent to call, For Example, All Tools?

    Like:

    Tools::all()
    

    This Should bring all Hammers and Screwdrivers because they are all Tools

    Is this possible using Eloquent?

  • The Alpha
    The Alpha almost 10 years
    You didn't say your problem properly but asked how to use an abstract class while that extends Eloquent. Anyways, glad to see your problem has been solved.
  • Gabriel Matusevich
    Gabriel Matusevich almost 10 years
    srry for the misinformation. I am used to deal with java/hibernate and its much more straight forward. ty anyways
  • AlphaCactus
    AlphaCactus over 6 years
    The Silverstripe framework also uses one table per model in its ORM. Very straightforward.