【Laravel】多対多のリレーションまとめ
参考:
Eloquent:リレーション 5.5 Laravel
今回は、例えばブログのカテゴリーのような、お互いが複数関わる
Laravelの多対多の関係について言及します。
個人的にはかなりハマりましたが言及された記事が少なかった。
Laravelの多対多関係について
ブログのカテゴリー、学校の学生と科目など、ブログが多くのカテゴリーを持ち、
カテゴリーも多くのブログを持つ(この記事もLaravelや多対多、Eloquentなど多くのカテゴリを持ちます)
ような関係の事を示しています。
今回はあるエントリーとカテゴリーを例にして言及することとします。
公式ドキュメントにも記載がありますが、1対1や、1対多より少し複雑です。
2つをつなぐ中間テーブルが必要になるからです。(モデルは必要ありません)
早速実装してみます!
中間テーブルやModelを作ってみる
今回の場合、categories、entries、そしてcategory_entryテーブルを使用します。
命名規則により、多対多関係になるテーブルの単数系をアルファベット順に並べます。
(無視してもできるそうですが、かなり面倒らしいので避けることをお薦めします。)
DBの構成
- entriesテーブル
- id
- title
- body
- created_at
- updated_at
- categoriesテーブル
- id
- name
- created_at
- updated_at
- category_entryテーブル(中間テーブル)
- id
- category_id
- entry_id
中間テーブルについては、1つの関係につき1つのデータになりますので
このブログの記事で例をあげると、
この記事のidが9の場合、下記のようになります。(多いので4つに省略しています)
つまり、1つの記事に対して4つのカテゴリが対応している場合、
4つのデータが必要になります。
DBにテーブルが登録できれば、次はmodelを作成していきます。
Modelの生成
中間テーブルについてはModelを必要としませんので、
Entryモデル、Categoryモデルを生成します。
まずはリレーション生成の部分のみ。
App\Models\Category.php
<?php class Category extends Model { public function entries() { return $this->belongsToMany('App\Models\Entry::class'); } }
App\Models\Entry.php
<?php class Entry extends Model { protected $fillable = [ 'title', 'body', ]; public function categories() { return $this->belongsToMany('App\Models\Category::class'); } }
今回は、多対多のため、belongsToManyメソッドを呼び出します。
準備はこれでOKです。
実践で、MVCモデルを利用した上で、データを入力する部分まで実装します。
カテゴリーは既にDBへ登録済みの前提で、
記事を投稿する時にカテゴリーの選択ができるような形で実装します。
(Viewはtwigを使用してますのでよしなに変更ください)
Controller/Model/View実装
View実装
<div class="container"> <form action="/entry/complete" method="post"> <div class="form-group"> <label for="f-title">Title</label> <textarea name="title" class="form-control" id="f-other"></textarea> </div> <div class="form-group"> <label for="f-category_ids">Category</label> <select name = "language_ids[]" class = "form-control" id = "f-language_ids" multiple> {% for id, name in all_categories_list %} <option value = "{{ id }}" >{{ name }}</option> {% endfor %} </select> </div> <div class="form-group"> <label for="f-body">body</label> <textarea name="body" class="form-control" id="f-body"></textarea> </div> <div class="form-group"> <input type="submit" value="send" class="btn" id="btn"> </div> </form> </div>
Controller実装
EntryControllerで入力フォームを表示する際、
既に登録済みのカテゴリーも表示します。
そして、Entryデータを保存する際に、
Entryデータと対応したカテゴリーのデータを保存するメソッドを呼び出します。
App/Controllers/EntryContoroller
<?php public function input(Request $request) //入力のViewを表示(その時にカテゴリーの配列も渡す) { return view('entry/input', [ 'all_categories_list' => $this->getAllCategoriesList() ]); } } public function complete(Request $request) { $inputs = $request->all(); $category_ids = $inputs['category_ids']; unset($inputs['category_ids']); //fillableしているのでEntryテーブルに入らないカラムは除く $id = $this->entry_model->saveAndGetId($inputs); $this->entry_model->saveCategoryIdsFromEntryId($id, $language_ids); return view('entry/complete'); }
Model実装
App/Models/Entry.php
<?php namespace App\Models; use App\Models\Language; use Illuminate\Database\Eloquent\Model; class Entry extends Model { protected function $fillable [ 'title', 'body', ]; public function saveAndGetId($inputs) { return $this->create($inputs)->id; //entryデータを生成した上で、中間テーブルに必要なIDを取り出す } public function saveLanguageIdsFromEntryId(int $id, array $language_ids)↲ { $entry = $this->find($id); //idからEntryを取り出して、 return $entry->languages()->attach($language_ids); //ここで中間テーブルに保存! }
App/Models/Category.php
<?php namespace App\Models; use App\Models; use Illuminate\Support\Collection; class Category extends Model { public function getAllCategoriesList() { return $this->pluck('name', 'id'); //すべてのカテゴリーを配列にして取り出す }
※関係の定義の部分は既出なので省略。
route/web.php
<?php Route::post('/entry/conplete' => 'EntryController@complete');
以上