Laravel モデルのリレーション:1対多

データベースのテーブルの多くは他のテーブルと関係性を持ちます。例えば posts テーブルで管理されているブログの投稿は comments テーブルで管理されている複数のコメントと関係しています。

テーブル同士の関係性をEloquent Modelを使えば簡単に定義することができます。今回はモデルを使った関係性の中でも1対多のリレーションについて定義と実際の利用方法をまとめます。

1対多を定義する

「SNSなどへの投稿(Post)」とそれに対する「コメント(Comment)」を想定してみましょう。

1対多の関係の場合、複数紐づくテーブルにもう一方のテーブルの id がわかるように post_id のようなカラムを持ちます。

post_id のように他のテーブルのどのデータか判別するためのカラムを「外部キー」と呼びます

f:id:kouVernon:20191012232659p:plain

これによって、例えば posts テーブルの1番の投稿に紐づくコメントは、 comments テーブルの1番、5番だということがわかります。

また comments テーブルの2番のコメントに紐づいている投稿は、 posts テーブルの2番ということがわかります。

使用するモデルの作成

Post モデルと Comment モデルを作成します。

$ php artisan make:model Post
$ php artisan make:model Comment

コマンドを実行したら app ディレクトリ配下に Post.php と Comment.php が作成されていればOKです👍

PostモデルにCommentモデルとのリレーションを定義する

Post モデルにはhasMany メソッドを利用して、 Commnet モデルとのリレーションを表す comments メソッドを実装できます。(1つの Post に対して複数の Comment が関係しているので、メソッド名は複数形です)

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public function comments()
    {
        return $this->hasMany('App\Comment');
    }
}

モデルは自動的に外部キーを判断してくれます。例えば上記の例だと、Comment モデルは post_id という外部キーを持っていると判断してくれます。

もしも Comment モデルが持っている外部キーが post_id では無い場合、例えば published_post_id という名前だった場合、以下のように hasMany メソッドの第二引数を修正します。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public function comments()
    {
        return $this->hasMany('App\Comment', 'published_post_id');
    }
}

リレーションを利用する

Model同士のリレーションの定義ができたら、コントローラーとbladeでどのように利用するのかをみていきましょう。

Postモデルに紐づいたCommentをコントローラーで扱う

今回はControllerの中で利用することを想定して、以下のような PostsController と indexアクション を作成しました。

※ Postモデルを利用するためにファイル上部で use App\Post; と書かれていることを確認してください。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Post;

class PostsController extends Controller
{
    public function index()
    {
        //
    }
}

1対多のリレーションは1つのデータに対して、複数のデータが紐づいていることを表す関係なので、まずはPost モデルを使ってデータを1つ見つけます。以下の例では id = 1 のデータを取得します。

public function index()
{
    $post = Post::find(1);
}

※ モデルをつかったデータ取得方法はこちらも記事を参考にしてください。
Laravel モデルの作成とデータ取得

1対多の関係の場合、Post モデルが1つ見つかると紐づく Comment モデルが見つかります。先ほど Post モデルで hasMany メソッドを使って定義した関数を利用することができます。

public function index()
{
    // id = 1のデータを取得
    $post = Post::find(1);

    // $postに紐づくCommentをCollectionとして取得
    $comments = $post->comments;

    // $commentsはCollectionなのでループができる
    foreach ($comments as $comment) {
        //
    }
}

これでコントローラーでモデルのリレーションを使ったデータ取得ができます。

Postモデルに紐づいたCommentをbladeで扱う

例えば PostsControllerindexアクション が以下のような実装だったとします。

public function index()
{
    $post = Post::find(1);
    return view('home', ['post' => $post]);
}

id = 1Post モデルを取得して、 home.blade.php というviewファイルを返しています。

bladeファイルで $post に紐づいている Comment モデルを利用するにはどうすればいいでしょうか。コントローラーと同じく、 先ほど Post モデルで hasMany メソッドを使って定義した関数を利用することができます。

以下の例では仮に Post モデルは title属性Comment モデルにも title属性 を持っているものとします。

{{-- $postのtitle属性を表示します --}}
<p>{{ $post->title }}</p>

{{-- $postに紐づくCommentモデルをCollectionとして取得し、かつtitle属性を表示します --}}
@foreach($post->comments as $comment)
  <p>{{ $comment->title }}</p>
@endforeach

$post に紐づくコメントはCollectionとして取得するのでループを利用することに注意

※ 個人的にはbladeで $post->commnets のように書くのではなく、コントローラーで変数に定義してから、bladeに渡してあげた方が読みやすいと思います。

これでbladeでモデルのリレーションを使ったデータ取得、表示ができます。

逆のリレーションを定義する

今まで Post モデルから紐づいている Commnet モデルを取得する方法をみてきましたが、逆に Commnet モデルから紐づいている1つの Post モデルを取得する方法をみていきます。

hasMany メソッドを利用したリレーションの逆を表現する時には belongsTo メソッドを利用します。Commnet モデルに以下のように post メソッドを実装してください。(1つの Commnet に対して1つの Post が関係しているので、メソッド名は単数形です)

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    public function post()
    {
        return $this->belongsTo('App\Post');
    }
}

上記の例では、 Commnet モデルが post_id という外部キーを持っているとLaravel側が自動的に判断してくれます。

もしも Commnet モデルが持っている外部キーが post_id では無い場合、例えば published_post_id という名前だった場合、以下のように belongsTo メソッドの第二引数を修正します。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    public function post()
    {
        return $this->belongsTo('App\Post', 'published_post_id');
    }
}

ここまで来るとあとは今までと同じです。コントローラーでもbladeでも Comment モデルに紐づいた Post モデルを利用することができます。

※ Commentモデルを利用するためにファイル上部で use App\Comment; と書かれていることを確認してください。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Comment;

class CommentsController extends Controller
{
    public function index()
    {
        // id = 1のデータを取得
        $comment = Comment::find(1);

        // $commentに紐づくPostモデルを取得
        $post = $comment->post;

        return view('home', ['comment' => $comment]);
    }
}
{{-- $commentのtitle属性を表示します --}}
<p>{{ $comment->title }}</p>

{{-- $commentに紐づくPostモデルを取得し、かつtitle属性を表示します --}}
<p>{{ $comment->post->title }}</p>

$comment に紐づく投稿は1つのためループは必要ないことに注意

まとめ

データベースのテーブルの多くは他のテーブルと関係性を持ちます。テーブル同士の関係性をEloquent Modelを使えば簡単に定義することができます。

今回はモデルを使った関係性の中でも1対多のリレーションについて定義と実際の利用方法をみてきました。

1対多の関係の場合、複数紐づくテーブルにもう一方のテーブルの id がわかるように post_id のようなカラムを持ちます。(このカラムを外部キーと言います)

リレーション(関係性)を定義する時には各モデルに hasMany メソッド、 belongsTo メソッドを利用して実装します。

コントローラーとbladeで利用する時にはモデルに実装した関数を使ってデータ取得をします。