WEBOPIXEL

Laravel5.4で基本的なリレーションを学んでみる

Posted: 2017.06.23 / Category: PHP / Tag: 

Laravel5.4のEloquentで基本的なリレーション(One To Many(1対多)とMany To Many(多対多))をしてみたいと思います。

Sponsored Link

基本的な構造は前回をベースとします。
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);

色々間違ってるかもしれませんが以上です。
リレーションは他にもありますが、とりあえずここさえ押さえておけばなんとかなるのかなとか。

ここまでのものはGithubにアップしています。