[Laravel] BelongsToで1対多のリレーション

今回はLaravelで1対多のデータを扱ってみます。1対多はデータベースのリレーションではbelongsToということが多いですね。今回は複数のチームを登録するteamsテーブル、メンバーを登録するmembersテーブルを用意します。
メンバーはどれか一つのteamに所属します。1つのチーム側から見ると複数のメンバーが登録されることになり、1対多の関係になります。teamsテーブルが親、membersテーブルが子の関係になっています。
メンバーの新規登録、更新(編集)の時に所属チームを登録したり変更したりするCRUD(Create, Read, Update, Delete)を作成してみます。
Contents
ModelとMigrationの作成
MemberモデルとTeamモデルを作成します。マイグレーションファイルも同時に作成します。
1 2 |
# php artisan make:model Member --migration # php artisan make:model Team --migration |
[モデル]
プロジェクトディレクトリ/app/Member.php
プロジェクトディレクトリ/app/Team.php
[マイグレーション]
プロジェクトディレクトリ/database/migrations/2020_04_13_154956_create_members_table.php
プロジェクトディレクトリ/database/migrations/2020_04_13_155219_create_teams_table.php
の4つのファイルが作られます。
マイグレーションの実行
まず、モデル(テーブル)にカラムを設定し、MySQLにテーブルを作成しましょう。
「2020_04_13_154956_create_members_table.php」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateMembersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('members', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('name'); $table->integer('team_id'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('members'); } } |
$table->bigIncrements(‘id’);はデフォルトだと $table->(‘id’);になっています。このままだとリレーションがうまく設定されずエラーになりました。ここはbigIncrementsを付けるのを忘れないようにしましょう。この原因を見つけるまで時間がかかってしまいました。
Laravel5.8からは主キーのデフォルトがincrements()からbigIncrements()に変更になっています。
・INTの範囲は-2147483648〜2147483647[21億4千7百万までのデータ]
・BIGINTの範囲は-9223372036854775808~9223372036854775807[20桁までの数値]
大きな数値を使わない場合はINTのほうがパフォーマンスが良いのでINTの設定の方が良いです。(今回はテストなのでパフォーマンスは無視してBIGINT(bigIncrements)を使っています。
マイグレーションではBigIncrementで生成されたidはunSingledBigIntegerで指定(idに-値は使わない)
「020_04_13_155219_create_teams_table.php」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateTeamsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('teams', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('name'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('teams'); } } |
こちらも $table->bigIncrements(‘id’);のbigIncrementsを忘れずに!
2つのマイグレーションファイルが作り終わったらコンソールからマイグレーションを実行します。
1 |
$ php artisan migrate |
Temamsテーブルには「サッカー・野球」のようなデータをphpMyAdminで入力しておきます。
モデル
「/app/Member.php」(子)のファイルを編集します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Member extends Model { protected $fillable = [ 'name', 'team_id', ]; public function team() { return $this->belongsTo('App\Team'); } } |
まずはお決まりの$fillableでカラムへのアクセスを許可します。
public function team(){}がリレーションの設定になります。
$this->belongsTo(‘/app/親モデル名’);と記載するだけでリレーションを作成してくれます。外部キーがteam_idという(小文字モデル名_id)になっている場合は、自動的にteam_idが外部キーとして処理されます。membersテーブルの中にteam_idを作成しておいたのは外部キーとして利用するためです。
「/app/Team.php」(親)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Team extends Model { protected $fillable = [ 'name', ]; public function members() { return $this->hasMany('App\Member'); } } |
親の方も子と同様に$fillableを設定します。
こちらは1対多の多(親)になるので、hasMany()を使います。
$this->hasMany(‘App\Member’);と子モデルを設定します。
これで2つのリレーションの設定は終わりです。
ルーティング
routes/web.phpでルーティングを設定します。
1 |
Route::resource('/member', 'MemberController'); |
この1行を記載するとCRUDに必要なルーティングが自動設定されます。
ルーティングを確認してみましょう。
1 |
# php artisan route:list |
index()
create()
store()
show()
edit()
update()
destroy()
というルーティングが作られています。
コントローラーの作成
データベースの作成、モデルの作成、ルーティングの設定が終わったので、今度はコントローラを作っていきます。
1 |
# php artisan make:controller MemberController --resource |
「app/Http/Controllers/MemberController.php」の設定
コントローラー部分は長くなりますが、ひとつひとつのアクションの中身はシンプルです。
コントローラー全体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Member; use App\Team; class MemberController extends Controller { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { $members = Member::all(); return view('member.index', compact('members')); } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { $teams = Team::all(); return view('member.create', compact('teams')); } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { Member::create($request->all()); return redirect()->route('member.index')->with('success', '登録完了'); } /** * Display the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function show($id) { // } /** * Show the form for editing the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function edit($id) { $teams = Team::all(); $member = Member::find($id); return view('member.edit', compact('member', 'teams')); } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param int $id * @return \Illuminate\Http\Response */ public function update(Request $request, $id) { $update = [ 'name' => $request->name, 'team_id' => $request->team_id ]; Member::where('id', $id)->update($update); return back()->with('success', '編集完了'); } /** * Remove the specified resource from storage. * * @param int $id * @return \Illuminate\Http\Response */ public function destroy($id) { // } } |
index()
1 2 |
$members = Member::all(); return view('member.index', compact('members')); |
「$members = Member::all();」では変数$membersに membersテーブルの全データを代入しています。
returnのところはデータをビューファイルに渡す設定です。
member/indexに$membersを渡しています。
compactは()の中に’members’と記載するだけで変数をビューに渡してくれる便利なメソッドです。
create()
1 2 |
$teams = Team::all(); return view('member.create', compact('teams')); |
情報の新規登録ではTeam名をセレクトできるようにします。セレクトボックスを作るのにTeamのデータが必要なのでTeamのデータを取得してmember/createに渡します。
store()
1 2 |
Member::create($request->all()); return redirect()->route('member.index')->with('success', '登録完了'); |
Member::create($request->all());は下記の2つを実行しています。
$request->all()で全データを連想配列で取得
Member::create()でmembersテーブルに新規データとして登録
「->with(‘success’, ‘登録完了’);」をつけて、登録完了の文字を登録させます。Laravelではこのように簡単にメッセージを表示させる仕組みが用意されています。
edit()
1 2 3 |
$teams = Team::all(); $member = Member::find($id); return view('member.edit', compact('member', 'teams')); |
「$teams = Team::all();」
編集の時にはセレクトボックスでTeamの値を使うので取得しています。
「$member = Member::find($id);」
編集する個別のデータをidで抽出
2つのデータをビューに渡しています。
update()
1 2 3 4 5 6 |
$update = [ 'name' => $request->name, 'team_id' => $request->team_id ]; Member::where('id', $id)->update($update); return back()->with('success', '編集完了'); |
$requestの中のname,team_idを$updateに代入。
「Member::where(‘id’, $id)->update($update);」でidを抽出し、そのidのデータを$updateのデータに上書きします。
「return back()->with(‘success’, ‘編集完了’);」は元のページに戻る、そして編集完了の文字を出力するという意味になります。
ビューファイル
index.blade.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<h1> Member List</h1> <p><a href="{{ route('member.create') }}">Create Record</a></p> @if ($message = Session::get('success')) <p>{{ $message }}</p> @endif <table border="1"> <tr> <th>Name</th> <th>Team</th> <th>Edit</th> </tr> @foreach ($members as $member) <tr> <td>{{ $member->name }}</td> <td>{{ $member->team->name }}</td> <th><a href="{{ route('member.edit',$member->id)}}">Edit</a></th> </tr> @endforeach </table> <?php echo '<pre>' . var_export($members, true) . '</pre>'; ?> |
create.blade.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<h1> Create Record </h1> <p><a href="{{ route('member.index')}}"> Member List </a></p> <form action="{{ route('member.store')}}" method="POST"> @csrf <p>Name<input type="text" name="name" value="{{ old('name') }}"></p> <p> <select name="team_id"> @foreach($teams as $team) <option value="{{ $team->id }}">{{ $team->name }}</option> @endforeach </select> </p> <input type="submit" value="Add Data"> </form> |
edit.blade.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<h1>Edit Member</h1> <p><a href="{{ route('member.index')}}"> Member List</a></p> @if ($message = Session::get('success')) <p>{{ $message }}</p> @endif <form action="{{ route('member.update',$member->id)}}" method="POST"> @csrf @method('PUT') <p>Name<input type="text" name="name" value="{{ $member->name }}"></p> <p> <select name="team_id"> @foreach($teams as $team) <option value="{{ $team->id }}" @if($team->id === $member->team_id) selected='selected' @endif>{{ $team->name }}</option> @endforeach </select> </p> <input type="submit" value="Edit"> </form> |
リレーションの確認
裏側ではどのようなデータが動いているのか見てみます。
index.blade.phpに
1 |
echo '<pre>' . var_export($members, true) . '</pre>'; |
を記載して「ドメイン名/member/index」にアクセスします。
長くなりますが、これが2件分のデーターです。Teamの内容が取得できていることがわかります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 |
Illuminate\Database\Eloquent\Collection::__set_state(array( 'items' => array ( 0 => App\Member::__set_state(array( 'fillable' => array ( 0 => 'name', 1 => 'team_id', ), 'connection' => 'mysql', 'table' => 'members', 'primaryKey' => 'id', 'keyType' => 'int', 'incrementing' => true, 'with' => array ( ), 'withCount' => array ( ), 'perPage' => 15, 'exists' => true, 'wasRecentlyCreated' => false, 'attributes' => array ( 'id' => 1, 'name' => 'Taro', 'team_id' => 1, 'created_at' => '2020-04-14 15:10:24', 'updated_at' => '2020-04-14 15:10:24', ), 'original' => array ( 'id' => 1, 'name' => 'Taro', 'team_id' => 1, 'created_at' => '2020-04-14 15:10:24', 'updated_at' => '2020-04-14 15:10:24', ), 'changes' => array ( ), 'casts' => array ( ), 'classCastCache' => array ( ), 'dates' => array ( ), 'dateFormat' => NULL, 'appends' => array ( ), 'dispatchesEvents' => array ( ), 'observables' => array ( ), 'relations' => array ( 'team' => App\Team::__set_state(array( 'fillable' => array ( 0 => 'name', ), 'connection' => 'mysql', 'table' => 'teams', 'primaryKey' => 'id', 'keyType' => 'int', 'incrementing' => true, 'with' => array ( ), 'withCount' => array ( ), 'perPage' => 15, 'exists' => true, 'wasRecentlyCreated' => false, 'attributes' => array ( 'id' => 1, 'name' => 'サッカー', 'created_at' => NULL, 'updated_at' => NULL, ), 'original' => array ( 'id' => 1, 'name' => 'サッカー', 'created_at' => NULL, 'updated_at' => NULL, ), 'changes' => array ( ), 'casts' => array ( ), 'classCastCache' => array ( ), 'dates' => array ( ), 'dateFormat' => NULL, 'appends' => array ( ), 'dispatchesEvents' => array ( ), 'observables' => array ( ), 'relations' => array ( ), 'touches' => array ( ), 'timestamps' => true, 'hidden' => array ( ), 'visible' => array ( ), 'guarded' => array ( 0 => '*', ), )), ), 'touches' => array ( ), 'timestamps' => true, 'hidden' => array ( ), 'visible' => array ( ), 'guarded' => array ( 0 => '*', ), )), 1 => App\Member::__set_state(array( 'fillable' => array ( 0 => 'name', 1 => 'team_id', ), 'connection' => 'mysql', 'table' => 'members', 'primaryKey' => 'id', 'keyType' => 'int', 'incrementing' => true, 'with' => array ( ), 'withCount' => array ( ), 'perPage' => 15, 'exists' => true, 'wasRecentlyCreated' => false, 'attributes' => array ( 'id' => 2, 'name' => 'Jiro', 'team_id' => 2, 'created_at' => '2020-04-14 15:10:53', 'updated_at' => '2020-04-14 15:10:53', ), 'original' => array ( 'id' => 2, 'name' => 'Jiro', 'team_id' => 2, 'created_at' => '2020-04-14 15:10:53', 'updated_at' => '2020-04-14 15:10:53', ), 'changes' => array ( ), 'casts' => array ( ), 'classCastCache' => array ( ), 'dates' => array ( ), 'dateFormat' => NULL, 'appends' => array ( ), 'dispatchesEvents' => array ( ), 'observables' => array ( ), 'relations' => array ( 'team' => App\Team::__set_state(array( 'fillable' => array ( 0 => 'name', ), 'connection' => 'mysql', 'table' => 'teams', 'primaryKey' => 'id', 'keyType' => 'int', 'incrementing' => true, 'with' => array ( ), 'withCount' => array ( ), 'perPage' => 15, 'exists' => true, 'wasRecentlyCreated' => false, 'attributes' => array ( 'id' => 2, 'name' => '野球', 'created_at' => NULL, 'updated_at' => NULL, ), 'original' => array ( 'id' => 2, 'name' => '野球', 'created_at' => NULL, 'updated_at' => NULL, ), 'changes' => array ( ), 'casts' => array ( ), 'classCastCache' => array ( ), 'dates' => array ( ), 'dateFormat' => NULL, 'appends' => array ( ), 'dispatchesEvents' => array ( ), 'observables' => array ( ), 'relations' => array ( ), 'touches' => array ( ), 'timestamps' => true, 'hidden' => array ( ), 'visible' => array ( ), 'guarded' => array ( 0 => '*', ), )), ), 'touches' => array ( ), 'timestamps' => true, 'hidden' => array ( ), 'visible' => array ( ), 'guarded' => array ( 0 => '*', ), )), ), )) |
関連記事
- [Laravel] MySQLのデータをLaravelで表示してみる
- [Laravel] MySQLのテーブル作成とテストデーター自動生成
- [Laravel] MySQLと連動させてCRUDを作成する
- [Vue.js] Vue-CLIでコンポーネントを動かす(基礎)
- [Laravel] Getの値をコントローラーで取得する方法