Laravel9でCRUDを実装する記事を書きました。
今回は上記で実装したモデルに対して、hasMany(1対n)のモデルを追加して、CRUD機能を実装します。
環境
以下の環境で実装しています。
2023年1月に実装した時点の情報になります。
- PHP:8.1.14
- Laravel:9.46.0
使用するhasMany(1対n)のモデル
本に紐づく読書記録というモデルの構成にしています。
book(親:1) → reading_histories(子:n)
モデルの実装
まず、親モデルの実装は、以下を追加します。
public function readingHistories()
{
return $this->hasMany(ReadingHistory::class);
}
子モデルの実装は、以下を追加します。
public function book()
{
return $this->belongsTo(Book::class);
}
ルーティングを追加
ルーティングは
books/1/reading-histories/1
のように、親子のIDをURLに含めるようにして、それぞれのモデルで、暗黙の結合をしています。
Route::controller(ReadingHistoriesController::class)->name('reading_histories.')->group(function() {
Route::get('books/{book}/reading-histories/create', 'create')->name('create');
Route::post('books/{book}/reading-histories', 'store')->name('store');
Route::get('books/{book}/reading-histories/{reading_history}', 'show')->scopeBindings()->name('show');
Route::get('books/{book}/reading-histories/{reading_history}/edit', 'edit')->scopeBindings()->name('edit');
Route::put('books/{book}/reading-histories/{reading_history}', 'update')->name('update');
Route::delete('books/{book}/reading-histories/{reading_history}', 'destroy')->scopeBindings()->name('destroy');
});
子データの一覧表示
以下のように、親データの表示に、子データの一覧表示を追加しました。
この場合、コントローラーには特に変更ありません。
以下のように、モデルの暗黙の結合を行っているだけで、その子データになる「reading_histories」も一緒にデータが取得されます。
これは、モデルに「$this->hasMany(ReadingHistory::class)」のように、hasManyの定義を追加して、リレーションされているためです。
public function show(Book $book)
{
return view('books.show', ['book' => $book]);
}
ビューは以下のようにしました。
「$book->readingHistories」とすることで、取得した子データが参照できます。
<div class="px-4 py-5 sm:px-6">
<h3 class="text-base font-semibold leading-6 text-gray-900">読書記録</h3>
</div>
<div class="border-t">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="py-4">
<a href="{{ route('reading_histories.create', $book->id) }}" class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white tracking-widest hover:bg-gray-700">新規登録</a>
</div>
<table class="w-full">
<tr>
<th>読了日</th>
<th>評価</th>
<th>感想</th>
<th></th>
</tr>
@foreach ($book->readingHistories as $reading_history)
<tr>
<td>{{ $reading_history->display_finished_date }}</td>
<td>{{ $reading_history->evaluation }}</td>
<td>{{ $reading_history->thoughts }}</td>
<td>
<form method="post" action="{{ route('reading_histories.destroy', [$book->id, $reading_history->id]) }}">
@csrf
@method('DELETE')
<a href="{{ route('reading_histories.show', [$book->id, $reading_history->id]) }}" class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white tracking-widest hover:bg-gray-700">詳細</a>
<a href="{{ route('reading_histories.edit', [$book->id, $reading_history->id]) }}" class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white tracking-widest hover:bg-gray-700">編集</a>
<button type="submit" onClick="return clickDelete()" class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white tracking-widest hover:bg-gray-700">削除</button>
</form>
</td>
</tr>
@endforeach
</table>
</div>
</div>
子データの登録
登録時のコントローラーは以下のようにしました。
$book->readingHistories()
と、親モデルから子モデルが参照できるので、それに対して、createメソッドを実行しています。
public function store(Request $request, Book $book)
{
$request->validate([
'finished_date' => 'nullable|date|before_or_equal:today',
'evaluation' => 'nullable|integer|between:1,5',
'thoughts' => 'nullable|string',
]);
$book->readingHistories()->create($request->all());
return Redirect::route('books.show', $book)->with('status', 'reading_histories-stored');
}
子データの表示
子モデルを暗黙結合しているので、子モデルをそのままビューに渡しています。
public function show(Book $book, ReadingHistory $reading_history)
{
return view('reading_histories.show', [
'book' => $book,
'reading_history' => $reading_history,
]);
}
子データの編集
表示の場合と同様、子モデルを暗黙結合しているので、子モデルに対してupdateメソッドを実行します。
public function update(Request $request, Book $book, ReadingHistory $reading_history)
{
$request->validate([
'finished_date' => 'nullable|date|before_or_equal:today',
'evaluation' => 'nullable|integer|between:1,5',
'thoughts' => 'nullable|string',
]);
$reading_history->update($request->all());
return Redirect::route('books.show', $book)->with('status', 'reading_histories-updated');
}
子データの削除
表示の場合と同様、子モデルを暗黙結合しているので、子モデルに対してdeleteメソッドを実行します。
public function destroy(Book $book, ReadingHistory $reading_history)
{
$reading_history->delete();
return Redirect::route('books.show', $book)->with('status', 'reading_histories-deleted');
}
また、削除に関しては、親データを削除した場合に、合わせて子データを削除することができますが、それが本記事では扱わず、別途書きたいと思います。