Laravel9で、hasMany(1対n)のCRUDを実装する

Laravel9で、hasMany(1対n)のCRUDを実装する Laravel

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');
}

また、削除に関しては、親データを削除した場合に、合わせて子データを削除することができますが、それが本記事では扱わず、別途書きたいと思います。

タイトルとURLをコピーしました