
今回はLaravelで1対多のデータを扱ってみます。1対多はデータベースのリレーションではbelongsToということが多いですね。今回は複数のチームを登録するteamsテーブル、メンバーを登録するmembersテーブルを用意します。
メンバーはどれか一つのteamに所属します。1つのチーム側から見ると複数のメンバーが登録されることになり、1対多の関係になります。teamsテーブルが親、membersテーブルが子の関係になっています。
メンバーの新規登録、更新(編集)の時に所属チームを登録したり変更したりするCRUD(Create, Read, Update, Delete)を作成してみます。
ModelとMigrationの作成
MemberモデルとTeamモデルを作成します。マイグレーションファイルも同時に作成します。
# 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」
<?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」
<?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つのマイグレーションファイルが作り終わったらコンソールからマイグレーションを実行します。
$ php artisan migrate
Temamsテーブルには「サッカー・野球」のようなデータをphpMyAdminで入力しておきます。
モデル
「/app/Member.php」(子)のファイルを編集します。
<?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」(親)
<?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でルーティングを設定します。
Route::resource('/member', 'MemberController');
この1行を記載するとCRUDに必要なルーティングが自動設定されます。
ルーティングを確認してみましょう。
# php artisan route:list
index()
create()
store()
show()
edit()
update()
destroy()
というルーティングが作られています。
コントローラーの作成
データベースの作成、モデルの作成、ルーティングの設定が終わったので、今度はコントローラを作っていきます。
# php artisan make:controller MemberController --resource
「app/Http/Controllers/MemberController.php」の設定
コントローラー部分は長くなりますが、ひとつひとつのアクションの中身はシンプルです。
コントローラー全体
<?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()
$members = Member::all(); return view('member.index', compact('members'));
「$members = Member::all();」では変数$membersに membersテーブルの全データを代入しています。
returnのところはデータをビューファイルに渡す設定です。
member/indexに$membersを渡しています。
compactは()の中に’members’と記載するだけで変数をビューに渡してくれる便利なメソッドです。
create()
$teams = Team::all(); return view('member.create', compact('teams'));
情報の新規登録ではTeam名をセレクトできるようにします。セレクトボックスを作るのにTeamのデータが必要なのでTeamのデータを取得してmember/createに渡します。
store()
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()
$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()
$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
<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
<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
<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に
echo '<pre>' . var_export($members, true) . '</pre>';
を記載して「ドメイン名/member/index」にアクセスします。
長くなりますが、これが2件分のデーターです。Teamの内容が取得できていることがわかります。
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 => '*', ), )), ), ))