ここでは学生の個人情報を表示するページを作成します.また,学生の履修情報を更新(変更)するためのフォームも作成します.特に,「学生」と「履修」という「多対多」のリレーションシップにおける中間テーブルに記録される履修情報を連鎖的に変更したり削除したりする方法についても説明します.
ここでは学生個人の情報を表示するためのページを作成します.このためにまずルート情報を定義します.例えば,/students/1 という URI では ID = 1 である井上さんのページを表示します.
routes/web.php(抜粋)
Route::get('/campuses', [CampusesController::class, 'index']) -> name('campuses.index');
Route::get('/campuses/{id}', [CampusesController::class, 'show']) -> name('campuses.show');
Route::get('/campuses/{id}/edit', [CampusesController::class, 'edit']) -> name('campuses.edit');
Route::patch('/campuses', [CampusesController::class, 'update']) -> name('campuses.update');
Route::delete('/campuses/{id}', [CampusesController::class, 'destroy']) -> name('campuses.destroy');
Route::get('/faculties', [FacultiesController::class, 'index']) -> name('faculties.index');
Route::get('/faculties/{id}', [FacultiesController::class, 'show']) -> name('faculties.show');
Route::get('/faculties/{id}/edit', [FacultiesController::class, 'edit']) -> name('faculties.edit');
Route::patch('/faculties', [FacultiesController::class, 'update']) -> name('faculties.update');
Route::get('/students', [StudentsController::class, 'index']) -> name('students.index');
Route::get('/students/{id}', [StudentsController::class, 'show']) -> name('students.show');
Route::get('/lectures', [LecturesController::class, 'index']) -> name('lectures.index');
次にコントローラに show 関数を定義します.とりあえずは引数として与えられた学生の ID だけを表示するような雛形を作成しておきます.
app/HTTP/Controllers/StudentsController.php(抜粋)
class StudentsController extends Controller
{
public function index()
{
$students = Student::get();
return view('students.index')
->with('students', $students);
}
public function show($id)
{
dd($id);
}
}
学生一覧ページ /students と講義一覧のページ /lectures に学生情報ページへのリンクを設置します.それぞれの index.blade.php を次のとおり編集します.
resources/views/students/index.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>学生一覧</title>
</head>
<body>
<h1>学生一覧</h1>
<ul>
@foreach ($students as $student)
<li>
{{ $student->id }} : <a href="{{ route('students.show', $student->id) }}">{{ $student->name }}</a>
<ul>
@foreach ($student->lectures as $lecture)
<li>{{ $lecture->name }}</li>
@endforeach
</ul>
</li>
@endforeach
</ul>
</body>
</html>
resources/views/lectures/index.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>講義一覧</title>
</head>
<body>
<h1>講義一覧</h1>
<ul>
@foreach ($lectures as $lecture)
<li>
{{ $lecture->name }}
<ul>
@foreach ($lecture->students as $student)
<li>{{ $student->id }} : <a href="{{ route('students.show', $student->id) }}">{{ $student->name }}</a></li>
@endforeach
</ul>
</li>
@endforeach
</ul>
</body>
</html>
ここまでの作業で学生一覧ページや講義一覧ページから学生個別のページの雛形へリンクされるようになりました.
次はコントローラの show 関数を編集して,実際に学生情報を取得してビューに渡す処理を書きます.
app/HTTP/Controllers/StudentsController.php(抜粋)
public function show($id)
{
$student = Student::where('id', $id)
->first();
if (!$student) {
return redirect('/students');
}
return view('students.show')
->with('student', $student);
}
さらにビューを作成します.resources/views/students/show.blade.php を作成して次のような内容にします.
resources/views/students/show.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{{ $student->name }}</title>
</head>
<body>
<h1>{{ $student->name }}</h1>
<p>学籍番号:{{ $student->id }}</p>
<p>氏名:{{ $student->name}}</p>
<h2>履修科目一覧</h2>
<ul>
@foreach ($student->lectures as $lecture)
<li>{{ $lecture->name }}</li>
@endforeach
</ul>
</body>
</html>
これで学生の個人情報表示ページが作成できました.
次は学生の履修情報を変更する機能を実装していきます.具体的には多対多のリレーションシップの中間テーブルを操作することになります.まず,履修情報を編集するページのためのルート定義と,実際に履修情報を更新する処理のルート定義を追加します.
routes/web.php(抜粋)
Route::get('/campuses', [CampusesController::class, 'index']) -> name('campuses.index');
Route::get('/campuses/{id}', [CampusesController::class, 'show']) -> name('campuses.show');
Route::get('/campuses/{id}/edit', [CampusesController::class, 'edit']) -> name('campuses.edit');
Route::patch('/campuses', [CampusesController::class, 'update']) -> name('campuses.update');
Route::delete('/campuses/{id}', [CampusesController::class, 'destroy']) -> name('campuses.destroy');
Route::get('/faculties', [FacultiesController::class, 'index']) -> name('faculties.index');
Route::get('/faculties/{id}', [FacultiesController::class, 'show']) -> name('faculties.show');
Route::get('/faculties/{id}/edit', [FacultiesController::class, 'edit']) -> name('faculties.edit');
Route::patch('/faculties', [FacultiesController::class, 'update']) -> name('faculties.update');
Route::get('/students', [StudentsController::class, 'index']) -> name('students.index');
Route::get('/students/{id}', [StudentsController::class, 'show']) -> name('students.show');
Route::get('/students/{id}/edit', [StudentsController::class, 'edit']) -> name('students.edit');
Route::patch('/students', [StudentsController::class, 'update']) -> name('students.update');
Route::get('/lectures', [LecturesController::class, 'index']) -> name('lectures.index');
また,学生の個人情報表示ページから履修登録編集のページへのリンクを作成します.
resources/views/students/show.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{{ $student->name }}</title>
</head>
<body>
<h1>{{ $student->name }}</h1>
<p>学籍番号:{{ $student->id }}</p>
<p>氏名:{{ $student->name}}</p>
<h2>履修科目一覧</h2>
<ul>
@foreach ($student->lectures as $lecture)
<li>{{ $lecture->name }}</li>
@endforeach
</ul>
<hr>
<p><a href="{{ route('students.edit', $student->id )}}">[履修科目の編集]</a></p>
</body>
</html>
次は,コントローラに edit と update という関数を追加します.なお,update 関数は更新ボタンが押されたときに呼び出されますが,フォームからどのような値が渡されてくるかを確認するコードだけを入力しておきます.まだ実際にはデータベースを更新しません.
app/HTTP/Controllers/StudentsController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Student;
use App\Models\Lecture; // 追加しておく
class StudentsController extends Controller
{
public function index()
{
$students = Student::get();
return view('students.index')
->with('students', $students);
}
public function show($id)
{
$student = Student::where('id', $id)
->first();
if (!$student) {
return redirect('/students');
}
return view('students.show')
->with('student', $student);
}
public function edit($id)
{
$student = Student::where('id', $id)
->first();
if (!$student) {
return redirect('/students');
}
$all_lectures = Lecture::get(); // 講義の一覧を取得しておく
return view('students.edit')
->with('student', $student)
->with('all_lectures', $all_lectures);
}
public function update(Request $request)
{
dd($request);
}
}
さらに編集画面のビューを作成します.具体的には resources/views/students/edit.blade.php を作成して次のような内容にします.
resources/views/students/edit.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{{ $student->name }}の編集</title>
</head>
<body>
<h1>{{ $student->name }}の編集</h1>
<div>
<form method="post"
action="{{ route('students.update') }}"
enctype="multipart/form-data">
@csrf
{{ method_field('patch') }}
<input type="hidden" name="id" value="{{ $student->id }}">
<p>
@foreach ($all_lectures as $lecture)
<input type="checkbox" name="risyu[]" value={{ $lecture->id }} id="risyu{{ $lecture->id }}">
<label for="risyu{{ $lecture->id }}">{{ $lecture->name }}</label> <br>
@endforeach
</p>
<p>
<input type="submit" value="履修科目の変更">
</p>
</form>
</div>
</body>
</html>
学生の個人情報ページを開き,「履修科目の編集」リンクを開きます.
ひとまず,各学生の編集ページで全ての講義科目についてチェックボックスが表示されました.
しかしながら,すでに履修中である科目にチェックが入っていないので,履修中の科目に予めチェックが入るようにします.このために,モデルファイル app/Models/Student.php に is_risyu()
というメソッドを追加します.これは,指定した科目を指定した学生が履修していれば 1 を,そうでなければ 0 を返す関数です.内部的には履修データの個数をカウントしていますが,2 以上の結果が得られることはないため,結果的に 0 か 1 の値を返します.
app/Models/Student.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB; // これを追加
class Student extends Model
{
use HasFactory;
// Many to Many
public function lectures()
{
return $this->belongsToMany('App\Models\Lecture');
}
// 履修しているかどうかを判定
public function is_risyu($lecture_id, $student_id)
{
$cnt = DB::table('lecture_student')
->where('lecture_id', $lecture_id)
->where('student_id', $student_id)
->count();
return $cnt;
}
}
上で定義した is_risyu
を使って,履修していればチェックを入れる処理を追加します.
resources/views/students/edit.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{{ $student->name }}の編集</title>
</head>
<body>
<h1>{{ $student->name }}の編集</h1>
<div>
<form method="post"
action="{{ route('students.update') }}"
enctype="multipart/form-data">
@csrf
{{ method_field('patch') }}
<input type="hidden" name="id" value="{{ $student->id }}">
<p>
@foreach ($all_lectures as $lecture)
<input type="checkbox" name="risyu[]" value={{ $lecture->id }} id="risyu{{ $lecture->id }}"
@if ($student->is_risyu($lecture->id, $student->id) == 1)
checked="checked"
@endif
>
<label for="risyu{{ $lecture->id }}">{{ $lecture->name }}</label> <br>
@endforeach
</p>
<p>
<input type="submit" value="履修科目の変更">
</p>
</form>
</div>
</body>
</html>
ここまでの作業で,編集ページを開くと学生がすでに登録している講義にだけ予めチェックが入るようになりました.
さらに,講義科目のチェックを変更して「履修科目の変更」ボタンを押下します.
するとフォームから送信されたパラメータを取得して表示することができました.具体的には,lecture_id
が 2 と 3 の講義(管理会計と情報ネットワーク論)にチェックされていたことを意味しています.
では実際にデータベースの更新を行う処理を記載します.具体的には指定した学生の履修を一旦すべて取り消した後,選択された講義の履修を登録するという方法を採用しています.
app/HTTP/Controllers/StudentsController.php(抜粋)
public function update(Request $request)
{
$student = Student::where('id', $request->id)
->first();
// dd($request->risyu);
// 一旦,すべての履修を取り消す(関連付けを削除する)
$student->lectures()->detach();
// 選択された講義の履修を登録する(関連付けを行う)
$student->lectures()->attach($request->risyu);
return redirect()->route('students.show', $request->id);
}
実際に管理会計と情報ネットワーク論にチェックを入れて「履修科目の変更」をクリックします.
履修登録の情報が正しく更新されていることがわかりました.
登録の変更前と変更後にデータベースの中間テーブルを確認すると次のような結果になります.上の update 関数では,detach()
によって関連付けを一旦全て削除してから attach()
で関連付けをやり直した関係で,井上さんは「管理会計」の履修登録を変更していないにもかかわらず,データベースの中間テーブルでも一旦削除されて再度登録されています.もしもこのような削除と再登録の処理を行いたくないような場合には,履修科目に変更があったかどうかを判断してから,履修が取り消された科目にだけ detach()
を実行し,新たに追加された科目にだけ
vagrant@ubuntu2204 laravelRelationship $ sqlite3 database/database.sqlite ⏎ SQLite version 3.38.2 2022-03-26 13:51:10 Enter ".help" for usage hints. sqlite> .tables ⏎ campuses lectures students faculties migrations users failed_jobs password_reset_tokens lecture_student personal_access_tokens sqlite> .headers ON ⏎ sqlite> SELECT * FROM lecture_student; ⏎ # 更新前 id|lecture_id|student_id|created_at|updated_at 1|1|1|| # 「経営戦略論」は削除されて OK 2|2|1|| # 「管理会計」まで一旦削除されてしまう 3|1|2|| 4|3|2|| 5|1|3|| 6|2|3|| 7|3|3|| 8|2|4|| 9|3|4|| sqlite> SELECT * FROM lecture_student; ⏎ # 更新後 id|lecture_id|student_id|created_at|updated_at 3|1|2|| 4|3|2|| 5|1|3|| 6|2|3|| 7|3|3|| 8|2|4|| 9|3|4|| 10|2|1|| # 「管理会計」を再登録 11|3|1|| # 「情報ネットワーク論」を登録 sqlite> .exit ⏎ vagrant@ubuntu2204 laravelRelationship $
最後に学生情報を削除する(つまり退学処理をする)機能を実装します.ここでは,学生情報を削除したときに連鎖的に履修登録のデータも削除することを考えます.まず,削除のためのルート情報を定義します.
routes/web.php(抜粋)
Route::get('/campuses', [CampusesController::class, 'index']) -> name('campuses.index');
Route::get('/campuses/{id}', [CampusesController::class, 'show']) -> name('campuses.show');
Route::get('/campuses/{id}/edit', [CampusesController::class, 'edit']) -> name('campuses.edit');
Route::patch('/campuses', [CampusesController::class, 'update']) -> name('campuses.update');
Route::delete('/campuses/{id}', [CampusesController::class, 'destroy']) -> name('campuses.destroy');
Route::get('/faculties', [FacultiesController::class, 'index']) -> name('faculties.index');
Route::get('/faculties/{id}', [FacultiesController::class, 'show']) -> name('faculties.show');
Route::get('/faculties/{id}/edit', [FacultiesController::class, 'edit']) -> name('faculties.edit');
Route::patch('/faculties', [FacultiesController::class, 'update']) -> name('faculties.update');
Route::get('/students', [StudentsController::class, 'index']) -> name('students.index');
Route::get('/students/{id}', [StudentsController::class, 'show']) -> name('students.show');
Route::get('/students/{id}/edit', [StudentsController::class, 'edit']) -> name('students.edit');
Route::patch('/students', [StudentsController::class, 'update']) -> name('students.update');
Route::delete('/students/{id}', [StudentsController::class, 'destroy']) -> name('students.destroy');
Route::get('/lectures', [LecturesController::class, 'index']) -> name('lectures.index');
次に,show.blade.php に削除のためのボタンを設置します.なお,単なるリンクではなく,セキュリティを高めるためにフォームにして,DELETE
メソッドを利用し,さらに CSRF トークンも仕込んでいることに注意してください.
resources/views/students/show.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{{ $student->name }}</title>
</head>
<body>
<h1>{{ $student->name }}</h1>
<p>学籍番号:{{ $student->id }}</p>
<p>氏名:{{ $student->name}}</p>
<h2>履修科目一覧</h2>
<ul>
@foreach ($student->lectures as $lecture)
<li>{{ $lecture->name }}</li>
@endforeach
</ul>
<hr>
<p><a href="{{ route('students.edit', $student->id )}}">[履修科目の編集]</a></p>
<div>
<form action="{{ route('students.destroy', $student->id )}}" method="post">
@csrf
{{ method_field('DELETE') }}
<button>学生の削除(退学処理)</button>
</form>
</div>
</body>
</html>
コントローラには destroy
メソッドを作成します.
app/HTTP/Controllers/StudentsController.php(抜粋)
public function destroy($id)
{
$student = Student::where('id', $id)
->first();
if (!$student) {
return redirect('/students');
}
$student->delete();
return redirect('/students');
}
これから学生レコードの削除に関する実験を行うので,データベースを初期化しておきます.
vagrant@ubuntu2204 laravelRelationship $ php artisan migrate:rollback; php artisan migrate; php artisan db:seed ⏎
実際に井上さん(ID = 1)のページから「学生の削除(退学処理)」ボタンを押して,レコードを削除します.
学生一覧を確認すると,井上さんの情報が削除されていることがわかりました.
さらに講義一覧のページでも井上さんの履修情報が連鎖的に削除されていることがわかりました.
井上さんの退学処理を行う前と後でデータベースの内容がどのように変化するかを確認すると次のようになりました.その結果 students テーブルのレコードを削除しただけで,中間テーブルである lecture_student から関連するレコードも連鎖的に削除されることがわかりました.これは中間テーブルのマイグレーションファイルにおいて onDelete('cascade')
が設定されているためです.CASCADE 以外にも,RESTRICT, SET NULL, NO ACTION などが利用できます.詳細は例えばこのWebページなどで確認してください.
vagrant@ubuntu2204 laravelRelationship $ sqlite3 database/database.sqlite ⏎ SQLite version 3.38.2 2022-03-26 13:51:10 Enter ".help" for usage hints. sqlite> .tables ⏎ campuses lectures students faculties migrations users failed_jobs password_reset_tokens lecture_student personal_access_tokens sqlite> .headers ON ⏎ sqlite> SELECT * FROM students; ⏎ # 削除前 id|name|created_at|updated_at 1|井上|| 2|藤井|| 3|飯田|| 4|足立|| 5|武田|| sqlite> SELECT * FROM lecture_student; ⏎ # 削除前 id|lecture_id|student_id|created_at|updated_at 1|1|1|| # 井上さんの履修情報 2|2|1|| # 井上さんの履修情報 3|1|2|| 4|3|2|| 5|1|3|| 6|2|3|| 7|3|3|| 8|2|4|| 9|3|4|| sqlite> SELECT * FROM students; ⏎ # 削除後 id|name|created_at|updated_at 2|藤井|| 3|飯田|| 4|足立|| 5|武田|| sqlite> SELECT * FROM lecture_student; ⏎ # 削除後 id|lecture_id|student_id|created_at|updated_at 3|1|2|| 4|3|2|| 5|1|3|| 6|2|3|| 7|3|3|| 8|2|4|| 9|3|4|| sqlite> .exit ⏎ vagrant@ubuntu2204 laravelRelationship $