基本的な構造は前回をベースとします。
Laravel5.4でシンプルなCMSを作るチュートリアル
Laravel 5.4
laravelcollective/html 5.4
を使用します。
One To Many(1対多)
Postは一つのCategoryに属している。
Categoryは複数のPostを持っている。
というのを例にやってみます。
テーブルの設定をします。
リレーションは「リレーション先のモデル名_id」でカラムを作成します。
posts
Schema::create('posts', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->text('body'); $table->integer('category_id')->nullable(); $table->timestamps(); });
categories
Schema::create('categories', function (Blueprint $table) { $table->increments('id'); $table->string('title'); });
モデルの設定
一つのCategoryに属しているPostモデルからリレーションの定義をしてみます。
category
メソッドにbelongsTo
を指定します。
app/Post.php
class Post extends Model { public function category() { return $this->belongsTo(Category::class); } }
Categoryは複数のPostを持っているのでhasMany
を指定します。
app/Category.php
class Category extends Model { public function posts() { return $this->hasMany(Post::class); } }
ビューの作成
post側のビューでは$post->category
のようにすると所属しているカテゴリーが表示できます。
{{ $post->category->title }}
詳細画面のように一つ表示なら問題ないのですが一覧ページはforeach
を使用して表示しますよね。
@foreach($posts as $post) {{ $post->category->title }} @endforeach
このような場合はN+1問題といって複数SQLクエリが発行されパフォーマンスが低下に繋がります。
これはコントローラーを下記のようにすることで解消できます。
app/Http/Controllers/Admin/PostsController.php
$posts = Post::with('category')->orderBy('id', 'desc')->paginate(20);
フォームの作成
関連付けするフォームも作成してみます。
Post側から一つのCategoryを選択したいので、セレクトボックスかラジオボタンがよさそうです。
ビューを表示する前にコントローラーでカテゴリー情報を渡します。
pluck
でidとtitleだけにしてtoArray
で配列にします。
app/Http/Controllers/Admin/PostsController.php
$categories = Category::pluck('title', 'id')->toArray(); return view('admin.posts.create', compact('categories'));
これでcategories
を展開してフォームを作成しましょう。
resources/views/admin/posts/fields.blade.php
{{-- ラジオボタン --}} @foreach ($categories as $key => $category) <label class="radio-inline"> {!! Form::radio('category_id', $key) !!} {{ $category }} </label> @endforeach {{-- セレクトボックス --}} {{ Form::select('category_id', $categories, isset($post->category->id) ? $post->category->id : null ) }}
Many To Many(多対多)
Postは複数のTagに持っている。
Tagは複数のPostを持っている。
というのを例にやってみます。
最初にテーブルの設定します。
One To Many で作成したpostsテーブルの他にtags
と2つを繋ぐ中間テーブルのpost_tag
テーブルを作成します。
tags
Schema::create('tags', function (Blueprint $table) { $table->increments('id'); $table->string('slug'); $table->string('title'); });
post_tag
Schema::create('post_tag', function (Blueprint $table) { $table->increments('id'); $table->integer('post_id'); $table->integer('tag_id'); });
モデルの設定
多対多はbelongsToMany
を指定します。
中間テーブルのモデルは必要ありません。
app/Post.php
class Post extends Model { public function tags() { return $this->belongsToMany(Tag::class); } }
app/Tag.php
class Tag extends Model { public function posts() { return $this->belongsToMany(Post::class); } }
ビューの作成
post側のビューでは$post->tags
のようにするとタグを表示できますが、カテゴリーと違い複数持ってるのでforeach
などを使用して表示します。
<p>タグ:</p> <ul> @foreach($post->tags as $tag) <li>{{ $tag->title }}</li> @endforeach </ul>
フォームの作成
複数関連付けの場合フォームはチェックボックスがよさそうです。
コントローラーでタグを配列で渡します。
app/Http/Controllers/Admin/PostsController.php
$tags = Tag::pluck('title', 'id')->toArray(); return view('admin.posts.create', compact('tags'));
resources/views/admin/posts/fields.blade.php
@foreach ($tags as $key => $tag) @php $isCheck = false; if (isset($post->tags) && $post->tags->contains($key)) { $isCheck = true; } @endphp <label class="checkbox-inline"> {!! Form::checkbox( 'tag_id[]', $key, $isCheck) !!} {{ $tag }} </label> @endforeach
こんな感じになりましたが、多分もっと簡潔にできる方法があるはず。
コントローラーの作成
1対多は基本的に一つのテーブルで完結していたので、コントローラーは基本そのまま使えたのですが、多対多の場合は中間テーブルがあるので、連動して更新する必要がありコントローラーも修正します。
まずは新規作成のstore
メソッド。
最後にattachすることでタグが作成されます。
$tags = $request->input('tag_id', []); unset($request['tag_id']); $post = Post::create($request->all()); $post->tags()->attach($tags);}
update
メソッドです。
sync
でタグも更新されます。
$post = Post::findOrFail($id); $post->tags()->sync($request->input('tag_id', [])); unset($request['tag_id']); $post->update($request->all());
destroy
メソッドです。
削除はdetach
です。
$post = Post::findOrFail($id); $post->tags()->detach(); $post->delete($id);
色々間違ってるかもしれませんが以上です。
リレーションは他にもありますが、とりあえずここさえ押さえておけばなんとかなるのかなとか。