理系公務員のプログラミング日記

【Laravel6】多対多のリレーションと実装(ホテル予約システム5)

タグ:
Laravel
授業課題の解説

多対多について

予約(Reservation)と部屋(Room)はお互いが相手に対して多の関係が成立する、多対多の関係になります。

  • ・一つの予約には、たとえば団体客の場合などは複数の部屋を予約する。
  • ・一つの部屋には、時間を変えて複数の予約が行われる。

この多対多という関係はデータベース上に直接表現することは推奨されません。表現できなくはないのですが、効率が悪いデータベースになってしまいます。

興味ある人はこれ読んでください。

そういったデータベースの無駄を防ぐために、中間テーブルという特殊なテーブルを追加してそこに表現します。 (ちなみに、中間テーブルというのはLaravel特有の呼び方で、連関エンティティと呼んだりします。)

参考:Laravel6.x Eloquent: リレーション 多対多

多対多の実装

今回は、予約と部屋の間にできるリレーションになるので、Reserveモデルに実装していきます。

belongsToManyは多対多を実装するために使用するメソッドです。

引数として、前から順番に下記の内容を書きます。

  • 1.相手のモデル
  • 2.中間テーブルの名前、
  • 3.中間テーブル上の相手のモデルの外部キー
  • 4.中間テーブル上の自分のモデルの外部キー

なお、これを実装するまでに、Roomモデルの作成や中間テーブルreserve_roomをmigrateし、それぞれにデータを挿入しているものとします。

Reserve.php

class Reserve extends Model { protected $primaryKey = 'reserve_id'; // 前回のguestとの1対多のリレーション public function guest() { return $this->belongsTo('App\Guest','guest_id','guest_id'); } // 今回のroomとの多対多のリレーション public function rooms() { // belongsToMany(相手のモデル、中間テーブルの名前、中間テーブル上の自分のモデルの外部キー、中間テーブル上の相手のモデルの外部キー) return $this->belongsToMany('App\Room','reserve_room','reserve_id','room_id') }

Viewでこのroom()メソッドを利用してみましょう。

Reserve/index.blade.php

@extends('layouts.hotel') @section('body') <table> <th>予約ID</th><th>お名前</th><th>ご住所</th><th>電話番号</th><th>人数</th><th>チェックイン日</th><th>チェックアウト日</th> <th>料金</th> @foreach($items as $item) <tr> <!----> <!-- roomメソッドのbelongToManyでroomモデルとその中間テーブルreserve_roomテーブルにアクセス --> <td>{{$item->rooms}}</td> </tr> @endforeach </table> @endsection

$item->roomで表示される内容の例

// 中間テーブルの1つ目のデータと対応するRoomsテーブル [{"room_id":1,"room_type_id":1,"room_number":"101","created_at":null,"updated_at":null, "pivot":{"reserve_id":1,"room_id":1}}, // 中間テーブルの2つ目のデータと対応するRoomsテーブル {"room_id":1,"room_type_id":1,"room_number":"101","created_at":null,"updated_at":null, "pivot":{"reserve_id":1,"room_id":1}}]

roomメソッドによるリレーションにより、データベースからRoomsテーブルの情報を取得できたことが分かります。

ここで利用したメソッドはbelongsToManyなので、item->roomsで取得できるデータは複数になります。

なので、配列を表す[ ]が使われています。

とりあえずfirst()メソッドを使って、一番前のデータを表示させます。

<td>{{$item->rooms->first()}}</td>

$item->rooms->first()で表示される内容の例

// 中間テーブルの1つ目のデータと対応するRoomsテーブルのレコード {"room_id":1,"room_type_id":1,"room_number":"101","created_at":null,"updated_at":null, "pivot":{"reserve_id":1,"room_id":1}}

これからさらに対応するRoomsテーブルのレコードを取得するには、

<td>{{$item->rooms->first()->room_number}}</td>

としましょう。

中間テーブルのデータを取得する。

pivotを日本語にすると、中間という意味になります。

中間テーブルのデータはこのpivotプロパティを介して取得できます。

<td>{{$item->rooms->first()->pivot}}</td>

$item->rooms->first()->pivot で表示される内容の例

// 中間テーブルの1つ目のデータと対応するRoomsテーブルのレコード {"reserve_id":1,"room_id":1}

ただし、このままでは対応する外部キーの値しか表示されません。

中間テーブルのそれ以外のカラムも表示するには、モデルのbelongsToManyのあとに、withPivot()を追加します。

Reserve.php

public function rooms() { return $this->belongsToMany('App\Room','reserve_room','room_id','reserve_id') ->withPivot('days','price'); }

修正後の$item->rooms->first()->pivot で表示される内容の例

// 中間テーブルの1つ目のデータと対応するRoomsテーブルのレコード {"reserve_id":1,"room_id":1,"days":"2021-10-23","price":8000}}