2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Laravel】 複数のチェックボックス(任意選択) 取扱方 (ついでに中間テーブルも)

Last updated at Posted at 2023-01-01

環境

  • Laravel: 8.6.2
  • Vue.js: 2.6.12
  • PHP: 8.0.8
  • Composer: 2.5.1
  • Node.js: 19.3.0
// envのDB接続部を書き換えておく
php artisan migrate:fresh
composer require laravel/ui
php artisan ui vue --auth
npm install
npm install resolve-url-loader@^5.0.0 --save-dev --legacy-peer-deps
npm install vue-loader@^15.9.8 --save-dev --legacy-peer-deps
composer require laravel/pint --dev
npm run dev
php artisan serve

概要

  • ユーザーが「興味のあるカテゴリを選択して登録する」画面を考えます
  • カテゴリは任意の数選択できますが、最低一つは選択しなければなりません
  • この時のバリデーション・エラーデザイン・データ登録の方法を以下に書いていきます

1. テーブルを作成する

  • users・user_category・categories の 3 テーブルを使用します
    • Users テーブルは既存のものを使用します
    • user_category, categories を以下で追加します
php artisan make:migration create_categories_table
php artisan make:migration create_user_category_table
categories_table.php
    public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->string('name')->unique();
            $table->timestamps();
        });
    }
user_category_table.php
    public function up()
    {
        Schema::create('user_category', function (Blueprint $table) {
            $table->unsignedBigInteger('user_id');
            $table->unsignedBigInteger('category_id');
            $table->timestamps();

            $table->foreign('user_id')
                ->references('id')
                ->on('users')
                ->onDelete('cascade');

            $table->foreign('category_id')
                ->references('id')
                ->on('categories')
                ->onDelete('cascade');
        });
    }

php artisan migrateできたら OK です

最後にcategoriesのサンプルデータだけseederで投入します

php artisan make:seeder CategorySeeder

Seederを書いていきます

CategorySeeder.php
<?php

namespace Database\Seeders;

use App\Models\Category;
use Illuminate\Database\Seeder;

class CategorySeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $categories = [
            [
                'name' => 'Frontend',
                'created_at' => now(),
                'updated_at' => now(),
            ],
            [
                'name' => 'Backend',
                'created_at' => now(),
                'updated_at' => now(),
            ],
            [
                'name' => 'Infra',
                'created_at' => now(),
                'updated_at' => now(),
            ]
        ];
            Category::insert($categories);
        
    }
}
DatabaseSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        if (config('app.env') === 'production') {
            Log::error('本番環境でSeederの一括実行はできません。処理を終了します。');

            return;
        }
        $this->call([
            CategorySeeder::class,
        ]);
    }
}

php artisan db:seedをして成功したら完了です!

2. モデルにリレーションを追記する

まず category のモデルを作成します

php artisan make:model Category

User モデルと Category モデルにリレーションを定義します

User.php
// ...
    public function categories() // <- 新しく追加
    {
        return $this->belongsToMany(Category::class, 'user_category')->withTimeStamps();
    }
Category.php
class Category extends Model
{
    use HasFactory;

    protected $fillable = [
        'name'
    ];

    public function users()
    {
        return $this->belongsToMany(User::class, 'user_category')->withTimestamps();
    }
}

3. 表示画面を作成する

既存の home.blade.php を編集する形で画面を作成します

home.blade.php
// ...
<div class="card-header">ユーザーにカテゴリを設定します</div>

<div class="card-body">
    @if (session('status'))
        <div class="alert alert-success" role="alert">
            {{ session('status') }}
        </div>
    @endif
    @if (session('categories'))
        <div class="alert alert-success" role="alert">
            登録されているカテゴリー
            <ul>
                @foreach (session('categories') as $row)
                    <li>{{ $row->name }}</li>
                @endforeach
            </ul>
        </div>
    @endif

<h3>{{ Auth::user()->name }}さん 興味のあるカテゴリを以下から選択してください</h3>

<form method="POST" action="{{ route('store') }}">
    @csrf
    <fieldset class="row mb-3">
        <legend class="col-md-4 col-form-label text-md-end">カテゴリー</legend>
        <div class="col-sm-5 col-form-label">
            <div class="form-check form-check-inline">
                <input class="form-check-input @error('categories') is-invalid @enderror"
                    type="checkbox" name="categories[]" id="frontend" value=1>
                <label class="form-check-label" for="frontend">
                    フロントエンド
                </label>
            </div>
            <div class="form-check form-check-inline">
                <input class="form-check-input @error('categories') is-invalid @enderror"
                    type="checkbox" name="categories[]" id="backend" value=2>
                <label class="form-check-label" for="backend">
                    バックエンド
                </label>
            </div>
            <div class="form-check form-check-inline">
                <input class="form-check-input @error('categories') is-invalid @enderror"
                    type="checkbox" name="categories[]" id="infra" value=3>
                <label class="form-check-label" for="infra">
                    インフラ
                </label>
            </div>
        </div>
    </fieldset>

    <div class="row mb-2">
        <div class="col-md-3 offset-md-1">
            <button type="submit" class="btn btn-outline-primary">登録
            </button>
        </div>
    </div>
</form>
// ...

次に Routing を追加します

web.php
// ...
Route::post('/store', [App\Http\Controllers\HomeController::class, 'store'])->name('store');

4. 登録処理を作成する

HomeController.php
    public function store(Request $request)
    {
        if (is_array($request->categories)) {
            User::find(Auth::user()->id)->categories()->sync($request->categories);
        }

        $categories = Auth::user()->categories;
        $status = 'カテゴリーを登録しました!';

        return redirect()->route('home')->with(compact('status', 'categories'));
    }

5. バリデーションを作成する

最低1つは選択してもらうようにします

php artisan make:request Home/StoreRequestで作成したファイルに記述します

StoreRequest.php
<?php

namespace App\Http\Requests\Home;

use Illuminate\Foundation\Http\FormRequest;

class StoreRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'categories' => ['required']
        ];
    }
}

作成したバリデーションをコントローラーで呼び出します

- use Illuminate\Support\Request
+ use App\Http\Requests\Home\StoreRequest; 

// ...

- public function store(Request $request)
+ public function store(StoreRequest $request)

エラーメッセージが表示されるようにViewも変更します

home.blade.php
// ...
<div class="card-body">
    @if ($errors->any())
        <div class="alert alert-danger mt-3">
            <ul>
                @foreach ($errors->all() as $error)
                    <li>{{ $error }}</li>
                @endforeach
            </ul>
        </div>
    @endif

6. バリデーションエラーデザインを記述する

  • 例えば他にも入力を受け付ける必須項目「一番興味のあるカテゴリ」があったとします
  • この項目がエラーとなって返ってきた時に、以前チェックを入れた項目が保存されません
  • 通常のinputであればoldメソッドを使うことでこの問題は解決するのですが、チェックボックスでは他の方法を探す必要があります...

バリデーションエラーだけであれば、Requestファイルに項目を追加するだけで発生させることができるので、実際に試してみましょう

StoreRequest.php
return [
    'categories' => ['required'],
    'name' => ['required']
];

次に3つのチェックボックスそれぞれのinputタグの中に、条件によってcheckedを付与する記述を追加します

home.blade.php
<div class="form-check form-check-inline">
    <input class="form-check-input @error('categories') is-invalid @enderror"
        type="checkbox" name="categories[]" id="frontend" value=1 @if (is_array(old('categories')) && in_array('1', old('categories'))) checked @endif>
    <label class="form-check-label" for="frontend">
        フロントエンド
    </label>
</div>

上記はvalue=1のフロントエンドに追記する例です
他2つはそれぞれのvalueに合わせてin_arrayメソッドの第一引数を2,3と変更してください

end.Githubのリポジトリ

今回作成したアプリケーションは以下に公開しています↓

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?