今回は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 => '*',
),
)),
),
))


