laravelpro_1対多とは

071対多とは

以下のコマンドを実行してマイグレーションファイルを作成します。

php artisan make:migration create_memo_table

作成したファイルのupメソッドを以下のように修正してください。

public function up(): void {
    Schema::create('memo', function (Blueprint $table) {
        $table->id();
        $table->integer('user_id')->nullable(false);
        $table->string('content', '255')->nullable(false);
        $table->integer('invalid')->default(0);
        $table->timestamps();
    });
}

先ほど作成したメモ帳の時のマイグレーションファイルとは内容が異なるので注意してください。

前回から増えたカラムは「user_id」「invalid」です。

まずはuser_idについてです。

user_idは「users」テーブルのidが入ります。

このような関係を1対多と言います。詳しくは以下で解説しています。

X(旧:Twitter)のようなSNSサービスで考えてみましょう。

あるユーザーが1日に5回ツイートした場合、このユーザーは3つのツイートを持っていることになります。このようにあるユーザー1人に対して複数ツイートを持っているような関係を1対多の関係と呼びます。

図で表すと以下の通りです。

こちらをER図にすると以下の通りです。

Laravelで実装

リレーションを定義

自テーブルから見て複数対応関係にある場合(親→子の関係)はhasMany()を使用します。(User → Tweet)

hasMany()の使い方

hasMany('関係するモデル', '関係するモデルで対応しているカラム名', '自テーブルで対応しているカラム名');

/* 省略 */
class User extends Model {
    public function tweets() {
        // 第一引数:紐づける先のモデル
        // 第二引数:相手テーブルのカラム名
        // 第三引数:自テーブルのカラム名
        return $this->hasMany(Tweet::class, 'user_id', 'id');
    }
}

逆のパターン(子→親の関係)はbelongsTo()を使用します。(Tweet → User)

belongsTo()の使い方

belongsTo('関係するモデル', '関係するモデルで対応しているカラム名', '自テーブルで対応しているカラム名');

/* 省略 */
class Tweet extends Model {
    public function user() {
        // 第一引数:紐づける先のモデル
        // 第二引数:相手テーブルのカラム名
        // 第三引数:自テーブルのカラム名
        return $this->belongsTo('User', 'user_id', 'id');
    }
}

Userに紐づくTweetを取得する方法

$tweets = User::find(1)->tweets->toArray();

/*
    $tweetsの中身例 
    [
        [
            'id' => 1,
            'user_id' => 1,
            'tweet' => 'hogehoge'
        ],
        [
            'id' => 2,
            'user_id' => 1,
            'tweet' => 'fugafuga'
        ]
    ]
*/

Tweetが誰のものか取得する方法

$user = Tweet::find(1)->user;
/*
    $userの中身例
    ※厳密にいうとuserは配列ではないが、見やすくするために配列っぽく書いてます。
    [
        'id' => 1,
        'name' => '太郎'
    ]
*/

多対多とは

再びTwitterのようなSNSサービスで考えてみましょう。

あるユーザーが「#松」「#竹」「#梅」という3つのハッシュタグを付けたツイートをする場合、このツイートは3つのハッシュタグを持っていることになります。 さらに、「#松」というハッシュタグが付けられているツイートも複数ツイート存在しています。

このような関係を多対多と呼びます。

図で表すと以下の通りです。(TweetAがハッシュタグを3つ持っている)

松から見た図で表すと以下の通りです。(同じハッシュタグが付けられているTweetが3つある)

こちらをER図にすると以下の通りとなります。

↑このテーブル定義で実装を進めていくと、tags.tweet_idは一つのtweet.idしか保持することができないため、多対多を表現することは難しくなってしまいます。(カラムを追加するなどすれば対応できるがよろしくない。)

このような問題を防ぐために中間テーブルというものを用いて多対多を表現していくことになります。

中間テーブル

中間テーブルは接続先のテーブルそれぞれの主キーを外部キーとして持たせる

今回の例でいうと、tagsテーブルとtweetsテーブルの主キーを中間テーブルに外部キーtweet_id、tag_idとして持たせます。

ER図にすると以下の通りになります。

Laravelで実装

リレーションを定義

多対多の場合はbelongsToManyを使用します。

使い方

belongsToMany('関係するモデル', '中間テーブルのテーブル名', '中間テーブル内で対応しているカラム名', '関係するモデルで対応しているカラム名');

実際にモデルに定義します。

/*
    省略
*/
class Tag extends Model
{
    public function tweets() {
        // 第一引数:紐づける先のモデル(※中間テーブルではない)
        // 第二引数:中間テーブル名
        // 第三引数:中間テーブル内で対応しているカラム名(自テーブルのidと中間テーブルのtag_idで紐づく)
        // 第四引数:関係するモデルで対応しているカラム名(結合先テーブルのidと中間テーブルのtweet_idで紐づく)
        return $this->belongsToMany(Tweet::class, 'tag_tweets', 'tag_id', 'tweet_id');
    }
}

/*
    省略
*/
class Tweet extends Model
{
    public function tags() {
        // 第一引数:紐づける先のモデル(※中間テーブルではない)
        // 第二引数:中間テーブル名
        // 第三引数:中間テーブル内で対応しているカラム名(自テーブルのidと中間テーブルのtweet_idで紐づく)
        // 第四引数:関係するモデルで対応しているカラム名(結合先テーブルのidと中間テーブルのtag_idで紐づく)
        return $this->belongsToMany(Tag::class, 'tag_tweets', 'tweet_id', 'tag_id');
    }
}

Tagに紐づくTweetを取得する方法

$ tweets = Tag :: find(1) -> tweets -> toArray();
/*
    $tweetsの中身例
    [
        [
            'id' => 1,
            'tweet' => 'hogehoge'
        ],
        [
            'id' => 2,
            'tweet' => 'fugafuga'
        ]
    ]
*/

Tweetに紐づくTagを取得する方法

$tags = Tweet::find(1)->tags->toArray();
/*
$tagsの中身例
[
  [
    'id' => 1,
    'name' => '#foo'
  ],
  [
    'id' => 2,
    'tweet' => '#foobar'
  ]
]
*/

つづいて「invalid」ですが、SQLの時に解説した論理削除を用いるためのカラムです。

削除には物理削除と論理削除があり、物理削除はレコード自体をDELETE文などで消してしまう削除方法です。メモアプリの時も物理削除で実装しました。

今回は論理削除で実装していきます。

論理削除はレコード自体を削除するのではなく、削除しているかどうかのフラグとなるカラムを用意し、そのカラムで削除されているかを判別する削除方法です。

例えば今回では「invalid」が削除判別フラグの役割を担います。invalidに0が入っているレコードは削除されていないレコード、1が入っているレコードは削除されたレコードして扱います。

当然テーブルから取得するときに、Memo::get();としてしまうと削除されたとされているレコードも取得されますが、Memo::where(’invalid’, 0)→get();とすることで、invalidが0のレコードだけ取得することができます。

今回はすべてのテーブルにinvalidを含めます。

それではマイグレーションを実行しましょう。

php artisan migrate

データベースを確認してみましょう。

// ターミナルが mysql> になってない場合
sudo mysql -u root

//
DESCRIBE memo;

memoテーブルの構成が表示されれば無事に作成されています。

画面を読み込んでみましょう。

メモは表示されていませんが先ほど作成したメモ帳が表示されました。

今のままではメモを追加できないので(テーブルのカラムを増やしたため)追加処理の部分を修正していきましょう。

MemoController.phpのadd()メソッドを修正

use Illuminate\\Support\\Facades\\Auth; // 追加

// 省略

public function add(Request $request) {
    // ユーザーIDを取得
    $user_id = Auth::id();

    // 追加
    $memo_text = $request->memo_text;
    $memo_model = new Memo();
    $memo_model->content = $memo_text;
    $memo_model->user_id = $user_id; // 追加

    $memo_model->save(); return redirect('/'); // 変更
}

これでメモが追加されるようになりました。

画面を読み込んでメモを追加してみましょう。

無事に追加できました。

コードを解説します。

$user_id = Auth::id();まずはこの部分ですが、Authファサードを使うと、ログインしているユーザーの情報を取得することができます。

今回はidだけ取得したいので、Auth::id()としています。

ユーザー名やメールアドレスなど他の情報を取得したい場合はAuth::user();で情報を一括で取得できます。

どのユーザーがメモを追加したかを判別するためユーザーIDを取得しています。

return redirect('/');この部分は、前回までself::show();でした。この状態だとURLが/addになり、再読み込みすると、読み込んだ回数分メモが増殖してしまいます。

増殖を防止するためにURLを/にリダイレクトさせます。

/にリダイレクトされるとshow()メソッドが実行されるので、挙動は同じになります。


投稿日

カテゴリー:

投稿者:

タグ:

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です