2
0

More than 1 year has passed since last update.

Laravelのリレーションごちゃごちゃになりがちな件

Last updated at Posted at 2022-01-22

どういうことか

laravelのリレーション、migrateファイルに書くだけならそこまで難しいことはないけど、
実際にmodelやbladeでhasOneとか使ってそれらしく書こうとすると
結構ごちゃごちゃになりがち(私だけかもしれないが)

Laravelのドキュメントは丁寧で分かりやすいが
実際のDBが載せられていないからちょっとイメージしづらくて、
「あれこれどっち何を書いたら?」ってなってしまったので、
実際の実装例を載せながら、備忘録として残しておく。

環境

laravel 8.17.0
mysql

前提

以下のような2つのテーブルがある。

アイドル名テーブル

アイドルの名前と、そのイメージ写真のファイル名が格納されたテーブル。
本来はいろいろな情報が格納されているがここは一旦分かりやすさ重視で簡略化。

id name age img_path
1 アイドルA 19 a.png
2 アイドルB 16 b.png
3 アイドルC 20 c.png
4 アイドルD 22 d.png

楽曲テーブル

楽曲の名前と、その楽曲を誰が歌っているかをidで格納したテーブル。
アイドルテーブルに対し、楽曲テーブルが1対多で紐づくことになる。
idol1、2、3それぞれに外部キー制約が貼られる。

id name idol1 idol2 idol3
1 楽曲X 1 2 4
2 楽曲Y 3
3 楽曲Z 2 3

上記の場合、楽曲XはアイドルA、B、Dの3人で歌唱する楽曲であるということを示す。

今回作成したいのは楽曲一覧ページ。
アイドルごとにimg_pathが設定されているのでこれを活用し、
外部キーで参照してimgタグに埋め込みたいのだ。

bladeでの実装イメージは以下。
songテーブルからidol1~3カラムを通じて、画像ファイル名を出力させたい。

song_list.blade.php
// あくまでイメージ
<td><img src="{{ asset('img/idol/' . $song->idol1->img_path) }}"></td>

migrateファイル

up部分だけを抜き出し

migrate.php
    public function up()
    {
        Schema::create('idol_list', function (Blueprint $table) {
            $table->id()->unique();
            $table->string('name')->comment('アイドル名');
            $table->tinyInteger('age')->comment('年齢');
            $table->string('image_path')->comment('画像パス');
        });
    }


    public function up()
    {
        Schema::create('song_list', function (Blueprint $table) {
            $table->id()->unique();
            $table->string('name')->comment('曲名');
            $table->bigInteger('idol1')->comment('歌唱アイドル1')->unsigned();
            $table->bigInteger('idol2')->comment('歌唱アイドル2')->unsigned();
            $table->bigInteger('idol3')->comment('歌唱アイドル3')->unsigned();

            $table->foreign('idol1')
                ->references('id')->on('idol_list')
                ->onDelete('cascade');
            $table->foreign('idol2')
                ->references('id')->on('idol_list')
                ->onDelete('cascade');
            $table->foreign('idol3')
                ->references('id')->on('idol_list')
                ->onDelete('cascade');
    }

ここまでできればjoinを使ってクエリビルダでごちゃごちゃ書くこともできるが、
今回は1つの列に複数の外部キー制約を擁するカラムがあるからとてもめんどくさそうな予感がする。
しかし上記のようにbladeでさくっと書くことができれば非常に見やすくわかりやすい形になる。

modelを作る

クエリビルダだけを使う場合はモデルがなくてもできるといえばできるが、
思想的にも保守的にもしっかり作っておいたほうがよい。
今回のbladeで対応する場合は必須になるので作成する。
クエリビルダでgetする方法では値しか取れず、外部キー連携の情報までは取れないようだ。

SongList.php
前略
class SongList extends Authenticatable
{
    use HasFactory, Notifiable;

    protected $table = 'song_list';

    protected $fillable = [
        'name',
        'idol1',
        'idol2',
        'idol3',
    ];
}
IdolList.php
前略
class IdolList extends Authenticatable
{
    use HasFactory, Notifiable;

    protected $table = 'idol_list';

    protected $fillable = [
        'name',
        'age',
        'img_path',
    ];
}

ざっくりこんなかんじ。

modelにリレーション処理を書く

ここからがついに本題。
今回はIdolListが親で、SongListが子になる、1対多の関係である。

IdolListは子モデルを持つ、つまりhasしている(?)ので、

IdolList.php
    public function song()
    {
        return $this->hasMany(SongList::class);
    }

このようにhasManyを介してSongListを持たせる。
メソッド名はなんでもよいが、分かりやすくするためsongとおいている。

またSongListはIdolListに属するといえるので、

SongList.php
    public function idol1()
    {
        return $this->belongsTo(IdolList::class, 'idol1', 'id');
    }
    public function idol2()
    {
        return $this->belongsTo(IdolList::class, 'idol2', 'id');
    }
    public function idol3()
    {
        return $this->belongsTo(IdolList::class, 'idol3', 'id');
    }

このように書くことができる。
この時外部キー制約を持たせているカラムが主キーでない場合は、第2、第3引数に値を渡すことで、
「このカラムが外部キー制約の対象のカラムですよ」と明示することができる。
今回idol1~3は主キーではないので、引数にて対象カラムを明示している。

上記の場合であれば、idol1を呼び出した場合にidol1とIdolListのidを自動的に紐づけてくれる。
※メソッド名とカラム名は必ずしも一致していなくてもよい。

ちょっと整理

親のモデルに書くのが

return $this->hasMany(子のモデル);

子のモデルに書くのが

return $this->belongsTo(親のモデル, '子の外部キー制約カラム', '親の外部キー対象カラム');

ここで、どっちにどのメソッドを書くのか、
第2と第3引数に何を渡すのかが、私の中でこんがらがった。

ついにbladeに起こすぞ

controllerで例えば以下のように記載し、bladeを呼び起こす準備をする。

SongController.php
    public function view_singer() {
        $song_list = SongList::all();
        return view('singer', [
            'title' => '歌唱者一覧',
            'song_list' => $song_list
        ]);
    }

bladeでは、先程貼ったリレーションを利用して以下のように記載できる。

singer.blade.php
@foreach ($song_list as $song)
<img src="{{ asset('img/idol/card_icon/' . $song->idol1->img_path) }}" >
<img src="{{ asset('img/idol/card_icon/' . $song->idol2->img_path) }}" >
<img src="{{ asset('img/idol/card_icon/' . $song->idol3->img_path) }}" >
@endforeach

$songはモデルなので、先程モデルに書いた処理を使うことができる。
で、先程SongList.phpに書いた処理といえばidol1~3のメソッドなのでそれらを呼ぶように記載。
後はIdolListにあるカラムを指定することで、そのカラムの内容を出力できるようになる。

細かな用語の使い方とかあやふやなところがあると思うので、
不自然に感じたらご指摘下さい。

2
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0