【Laravel】中間テーブルを使ってみる (応用編)

えび

Laravel中間テーブルを活用して
リレーションチェックwhere検索などを実装してみる

登録フォームを作ってみる

以前の記事 (基本編) では、
itemsテーブルとcategoriesテーブルの中間テーブルを作成した

そこで、商品名と複数のカテゴリを選択できるような登録フォームを作ってみる

  • routes/web.php
    ルーティングはCRUDでサクッと作る
Route::resource('items', App\Http\Controllers\Admin\ItemController::class);

  • resources/views/create.blade.php
<form method="POST" action="{{ route('items.store') }}">
    @csrf
    
    <label for="name" class="col-md-6 col-form-label text-left">
      商品名
    </label>
    <div class="col-md-6 m-auto">
        <input id="name"
               type="text"
               class="form-control"
               name="name"
               value="{{ old('name') }}"
        />
    </div>

    <label for="category_id" class="col-md-6 col-form-label text-left">
        カテゴリ
    </label>
    <div class="col-md-6 m-auto">
        @foreach ($categories as $category)
            <label>
                <input type="checkbox"
                       name="category_ids[]"
                       value="{{ $category->id }}"
                 />
                {{ $category->name }}
            </label>
        @endforeach
    </div>

    <button type="submit" class="btn btn-primary">
        登録する
    </button>
</form>

  • ItemController.php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\Item;
use Illuminate\Http\Request;

class ItemController extends Controller
{
    protected Item $item;

    /**
     * ItemController constructor.
     * @param Item $item
     */
    public function __construct(Item $item)
    {
        $this->item = $item;
    }

    /**
     * 登録フォーム画面
     */
    public function create()
    {
        $categories = Category::all();
        return view('create', compact('categories'));
    }

    /**
     * 登録処理
     */
    public function store(Request $request)
    {
        $item = $this->item->create([
            'name' => $request->get('name'),
        ]);
        // ⭐️ リレーション登録
        $item->categories()->sync($request->get('category_ids', []));
    }
}

こんな感じ

続いて編集フォームを作ってみる

基本的には登録フォームと同じだけど、
そのままだと既にリレーションされている場合でも、チェックが外れた状態になってしまう
そのため既にリレーションされている場合はチェック済みにしておくという配慮を入れてみる

  • edit.blade.php
<form method="POST" action="{{ route('items.update', compact('item')) }}">
    @method('PUT')
    @csrf
    
    <label for="name" class="col-md-6 col-form-label text-left">
        商品名
    </label>
    <div class="col-md-6 m-auto">
        <input id="name"
               type="text"
               class="form-control"
               name="name"
               value="{{ old('name', $item->name) }}"
        />
    </div>

    <label for="category_id" class="col-md-6 col-form-label text-left">
        カテゴリ
    </label>
    <div class="col-md-6 m-auto">
        @foreach ($categories as $category)
            <label>
                <input type="checkbox"
                       name="category_ids[]"
                       value="{{ $category->id }}"
                       {{ $item->categories->contains($category->id) ? 'checked' : '' }}
                 />
                {{ $category->name }}
            </label>
        @endforeach
    </div>

     <button type="submit" class="btn btn-primary">
          更新する
     </button>
</form>

リレーション済みかチェックするcontains()

上記viewソースでリレーション済みか判断しているのはここ

<input type="checkbox"
       name="category_ids[]"
       value="{{ $category->id }}"
       {{ $item->categories->contains($category->id) ? 'checked' : '' }}
/>

BelongsToManyで用意してくれているcontains($チェックするID)メソッドを利用して、
そのリレーションに既にそのIDが含まれているかをチェックできる

  • ItemController.php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\Item;
use Illuminate\Http\Request;

class ItemController extends Controller
{
    protected Item $item;

    /**
     * ItemController constructor.
     * @param Item $item
     */
    public function __construct(Item $item)
    {
        $this->item = $item;
    }

    // (登録関連は省略)

    /**
     * 編集フォーム画面
     */
    public function edit(Item $item)
    {
        $categories = Category::all();
        return view('form', compact('item', 'categories'));
    }

    /**
     * 更新処理
     */
    public function update(Item $item, Request $request)
    {
        $item->update([
            'name' => $request->get('name'),
        ]);
        // ⭐️ リレーション更新
        $item->categories()->sync($request->get('category_ids', []));
    }
}

中間テーブルを検索してみる

話はガラッと変わり、商品の一覧表示などでカテゴリ検索したい場合の実装に関して

viewは省略するけど、
例えば$requestに「category_id」が含まれている場合は検索が必要とする
ここではwhereHas()メソッドを用いてリレーション検索してみる

  • ItemController.php
public function index(Request $request)
{
    $query = Item::query();

    if (!empty($request->get('category_id'))) {
        // ⭐️ リレーション先のカテゴリ検索
        $query->whereHas('categories', function ($query) use ($request) {
            $query->where('categories.id', $request->get('category_id'));
        });
    }

    $items = $query->orderBy('created_at', 'desc')->get();
    
    return view('items', compact('items'));
}

〜他、随時加筆予定〜