如何使用 Laravel 的 Eloquent 实现单表继承?

Posted

技术标签:

【中文标题】如何使用 Laravel 的 Eloquent 实现单表继承?【英文标题】:How can I implement single table inheritance using Laravel's Eloquent? 【发布时间】:2014-12-28 18:30:51 【问题描述】:

目前我有一个名为 Post 的模型类。

class Post extends Eloquent 

    protected $table = 'posts';
    protected $fillable = array('user_id', 'title', 'description', 'views');

    /*
     * Relationships
     */

    public function user()
    
        return $this->belongsTo('User');
    

    public function tags()
    
        return $this->belongsToMany('Tag', 'post_tags');
    

    public function reactions()
    
        return $this->hasMany('Reaction');
    

    public function votes()
    
        return $this->hasMany('PostVote');
    

    //Scopes and functions...

我想将帖子分为两种不同的类型; articlesquestions。我认为最好的方法是通过继承,所以ArticleQuestion 将扩展Post。最好的方法是什么?从哪里开始?

【问题讨论】:

在数据库中它仍然是一张表吗? (例如定义类型的列) 对不起,我很困惑!在帖子表中添加type 列就足够了,因为文章和问题之间没有区别。唯一的问题是文章有一个缩略图,但我可以留下那个专栏NULL 来提问(对吗?)。 是的,如果只有一列不同,我会说完全可以将其保留为空。但是,如果这种情况发生变化,您可能希望将其拆分为多个表以减少未使用的列。那么现在,您的问题是否仍需要帮助,还是要单独使用 Post 模型? 是的,我很想听听您的回答,因为唯一的区别可能不是缩略图列。我需要这个来进一步发展。 【参考方案1】:

在深入探讨多表 继承之前,我想先说几句关于单表 继承的内容。当涉及到 db 模型的继承时,单表继承是更简单的方法。 您有多个模型绑定到同一个表和一个type 列来区分不同的模型类。但是,您通常希望实现继承的原因是模型具有共享属性,但也具有模型独有的一些属性。 当使用单表继承时,您的表在某些时候看起来类似于:

id   shared_column   question_column   article_column   question_column2   article_column2 etc...
1    Lorem           62                NULL             test               NULL
2    Ipsum           NULL              x                NULL               true

您最终会得到很多 NULL 值,因为某些类型的模型不需要列。如果记录很多,这会影响数据库的大小。

但在某些情况下,它可能仍然是最佳解决方案。这是一个写得很好的Tutorial,它展示了如何在 Laravel 中以一种非常优雅的方式实现它。

多表继承

现在让我们看看多表继承。使用这种方法,您可以将单个表拆分为多个表(好吧,我猜这个名字已经放弃了这种方法;))我们将使用一种称为 Polymorphism

的技术

上例中的架构如下所示:

posts table:

id   shared_column  postable_id  postable_type
1    Lorem          1            Question
2    Ipsum          1            Article


questions table:

id   question_column   question_column2
1    62                test


articles table:

id   article_column   article_column2
1    x                true

如果你问我会更干净...

这里有趣的列是postable_idpostable_type。类型告诉我们将在哪个表上找到模型的“其余部分”,id 指定属于它的记录的主键。请注意,列名可以是您想要的任何名称,但按照惯例将其称为 "-able"

现在让我们看看 Eloquent 模型。

发帖

class Post extends Eloquent 
    // all your existing code
    
    public function postable()
        return $this->morphTo();
    

问题 / 文章 / 所有其他可发布类型

class Question extends Post 
    public function post()
        return $this->morphOne('Post', 'postable');
    

请注意,您实际上不必从 Post 扩展,但如果您可能也有想要使用的方法,则可以。无论如何,无论有没有它,多态关系都会起作用。

好的,这是基本设置。以下是使用新模型的方法:

检索所有帖子

$posts = Post::all();

检索所有问题

$questions = Question::all();

从帖子中获取问题列

$post = Post::find(1);
$question_column2 = $post->postable->question_column2;

从问题中获取帖子属性

$question = Question::find(1);
$shared_column = $question->post->shared_column;

检查帖子的类型

$post = Post::find(1);
echo 'type: '.get_class($post->postable);
if($post->postable instanceof Question)
    // Looks like we got a question here

创建新问题

现在请耐心等待,创建模型有点复杂。如果您必须在应用程序的多个位置执行此操作,我建议您为其编写一个可重用的函数。

// create a record in the questions and posts table

$question = new Question();
$question->question_column = 'test';
$question->save();

$post = new Post();
$post->shared_column = 'New Question Post';
$post->save();

// link them together

$question->post()->save($post);

如您所见,干净的数据库是有代价的。处理你的模型会有点复杂。但是,您可以将所有这些额外的逻辑(例如创建模型所需的逻辑)放入模型类中的函数中,而不必太担心。

另外,还有一个不错的 Tutorial 用于 laravel 的多表继承。也许它有帮助;)

【讨论】:

这太棒了,清晰明了,我本来可以要求的。非常感谢! 不客气。我希望这个答案将来也能帮助其他人:) 我一直在寻找一个多表继承示例。如此停留在 symfony 的思维方式中,我没有意识到你可以利用多态关系来模拟行为。谢谢! 这个响应启发了我编写一种方法来自动化和简化通过许多表编写对象的过程,通过 laravel 多态关系实现模型继承。我很高兴听到您对此的看法:***.com/questions/48445727/… @lukasgeiter 这很好,直到您需要删除帖子。因为关系是相反的,所以当你删除一个 Question 时,当然 Post 也会被删除。但是如果我们删除帖子呢?当我们删除帖子时,与它相关的任何内容,无论是问题还是文章都应该删除。你是怎么处理的?【参考方案2】:

从 Laravel 5.2 开始,Global Scope 可用:

class Article extends Post

    protected $table = 'posts';

    protected static function boot()
    
        parent::boot();

        static::addGlobalScope('article', function (Builder $builder) 
            $builder->where('type', 'article');
        );

        static::creating(function ($article) 
            $article->type = 'article' 
        ); 
    

where type = 'article' 被添加到Article 的所有查询中,就像SoftDeletes 一样。

>>> App\Article::where('id', 1)->toSql()
=> "select * from `posts` where `id` = ? and `type` = ?"

其实 laravel 使用这个特性提供了SoftDeletes trait。

【讨论】:

我的猜测是您的代码在选择时会做得很好,但是对于创建新文章,它需要一些额外的工作...... 是的,我们应该像这样钩住creating `` static::creating(function ($article) $article->type = 'article' ); ``` 我在答案中添加了static::creating

以上是关于如何使用 Laravel 的 Eloquent 实现单表继承?的主要内容,如果未能解决你的问题,请参考以下文章

Laravel Eloquent - orWhereHas 方法 - 何时使用以及如何使用

如何使用 Laravel 4 Eloquent 连接列?

如何在 Laravel 5.8 中使用 Eloquent 进行查询?

如何在 Laravel/Eloquent 中获得完整的关系

如何在 laravel 5 中使用 Eloquent 更新数据透视表

如何使用 Laravel 的 eloquent 加载相同的列名?