Как реализовать одиночное наследование таблиц с помощью Laravel Eloquent?

В настоящее время у меня есть класс модели с именем 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...
}

Я хотел бы разделить сообщения на два разных типа; articles и questions. Я думал, что лучший способ сделать это - наследование, поэтому Article и Question будут расширять 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 довольно элегантным способом.

Наследование нескольких таблиц

Теперь рассмотрим многостраничное наследование. С помощью этого метода вы разделите свою единственную таблицу на несколько (ну, я думаю, имя уже дало такой вид уже;)) Мы собираемся использовать технику под названием Полиморфизм

Наша схема из приведенного выше примера будет выглядеть так:

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_id и postable_type. Тип говорит нам, в какой таблице мы найдем наш "отдых" модели, а id указывает первичный ключ принадлежащей ему записи. Обратите внимание, что имена столбцов могут быть любыми, что вы хотите, но условно назвать его "-able".

Теперь взглянем на модели Eloquent.

Сообщение

class Post extends Eloquent {
    // all your existing code

    public function postable(){
        return $this->morphTo();
    }
}

Вопрос/ Article/ любой другой тип сообщения

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. Может быть, это помогает;)

Ответ 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');
        });
    }
}

where type = 'article' добавляется ко всему запросу Article точно так же, как SoftDeletes.

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

На самом деле laravel предоставляет черту SoftDeletes, используя эту функцию.