【Laravel6】Bootstrap4で横並びにしたラジオボタンのバリデーションメッセージを表示する時の落とし穴

## 概要

Laravelアプリでコンテンツ登録時にBootstrapで作成したラジオボタンのバリデーションメッセージが表示されない事態に陥りました。

本記事ではその解決方法の一例を紹介します。

※Laravelアプリで発生しましたが、原因はBootstrapだったのでLaravel特有なものではないです。

## 環境

```
$ composer -V
Composer version 1.10.20 2021-01-27 15:41:06

$ php artisan --version
Laravel Framework 6.20.16
```

## 前提

バリデーションメッセージは**各入力項目の下**に分けて表示させる。
（「カテゴリーは必ず指定してください。」がバリデーションメッセージです）

![スクリーンショット 2021-02-22 7.39.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512695/2a9de463-f3e4-1bff-d744-e568a02639e9.png)


よくLaravel教材で採用されているのは、画面上部に全てのバリデーションメッセージを箇条書きで表示する方法で以下のコード。
（公式ドキュメントより）

```php
<!-- /resources/views/post/create.blade.php -->

<h1>ポスト作成</h1>

@if ($errors->any())
    <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif

<!-- ポスト作成フォーム -->
```

今回はこちらのコード（例）を採用します。
（こちらも公式ドキュメントより）

```php
<!-- /resources/views/post/create.blade.php -->

<label for="title">Post Title</label>

<input id="title" type="text" class="@error('title') is-invalid @enderror">

@error('title')
    <div class="alert alert-danger">{{ $message }}</div>
@enderror
```

## Bootstrapで横並びにしたラジオボタンのバリデーションメッセージを表示する時の落とし穴の解決方法

### 解決済みのコード

```php:create.balde.php
{{-- 略 --}}

<div class="form-group mb-4">
    <p for="exampleInputPassword1">カテゴリー<span class="text-danger">(※)</span></p>
    {{-- ラジオボタン  --}}
    <div class="form-check form-check-inline is-invalid">
        <input class="form-check-input form-check-inline is-invalid " type="radio" name="category_id" id="category1" value="1" >
        <label class="form-check-label" for="category1">Laravel</label>
    </div>                        
    <div class="form-check form-check-inline is-invalid">
        <input class="form-check-input form-check-inline is-invalid " type="radio" name="category_id" id="category2" value="2" >
        <label class="form-check-label" for="category2">PHP</label>
    </div>                        
    <div class="form-check form-check-inline is-invalid">
        <input class="form-check-input form-check-inline is-invalid" type="radio" name="category_id" id="category3" value="3" >
        <label class="form-check-label" for="category3">Docker</label>
    </div>                        
    <div class="form-check form-check-inline is-invalid">
        <input class="form-check-input form-check-inline is-invalid" type="radio" name="category_id" id="category4" value="4" >
        <label class="form-check-label" for="category4">web基礎</label>
    </div>
    {{-- バリデーションメッセージ --}}
    <span class="invalid-feedback" role="alert">
        <strong>カテゴリーは必ず指定してください。</strong>
    </span>
</div>
{{-- 略 --}}
```

### 落とし穴にハマった時のコード

```php:create.balde.php
{{-- 略 --}}

<div class="form-group mb-4">
    <p for="exampleInputPassword1">カテゴリー<span class="text-danger">(※)</span></p>
    {{-- ラジオボタン  --}}
    <div class="form-check form-check-inline">
        <input class="form-check-input form-check-inline is-invalid" type="radio" name="category_id" id="category1" value="1" >
        <label class="form-check-label" for="category1">Laravel</label>
    </div>                        
    <div class="form-check form-check-inline">
        <input class="form-check-input form-check-inline is-invalid" type="radio" name="category_id" id="category2" value="2" >
        <label class="form-check-label" for="category2">PHP</label>
    </div>                        
    <div class="form-check form-check-inline">
        <input class="form-check-input form-check-inline　is-invalid" type="radio" name="category_id" id="category3" value="3" >
        <label class="form-check-label" for="category3">Docker</label>
    </div>                        
    <div class="form-check form-check-inline">
        <input class="form-check-input form-check-inline is-invalid" type="radio" name="category_id" id="category4" value="4" >
        <label class="form-check-label" for="category4">web基礎</label>
    </div>
    {{-- バリデーションメッセージ --}}
    <span class="invalid-feedback" role="alert">
         <strong>カテゴリーは必ず指定してください。</strong>
    </span>
</div>
{{-- 略 --}}
```

これだとバリデーションメッセージが表示されません。

![スクリーンショット 2021-02-22 7.48.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512695/78f118e0-a36a-75c5-ae34-5ba46b781f66.png)

### 違いと解決理由

#### 違い

１つ１つのラジオボタン（`input`タグの`type=radio`）の親のクラスに`div`タグに`is-invalid`がつけているかついていないか

バリデーションメッセージが表示される

```php
<div class="form-check form-check-inline is-invalid">
```

バリデーションメッセージが表示されない

```php
<div class="form-check form-check-inline>
```

#### 解決理由

バリデーションエラー時に入力フォームを赤くして、メッセージを表示させるには以下のルールがあったのですが、シンプルにそれを満たしていなかっただけでした。

- ラジオボタンのinputタグのクラスに`is-invalid`がついている
- バリデーションメッセージを表示するDOMの兄弟要素（同じ階層）のタグのクラスにそれぞれ`is-invalid`、`invalid-feedback`がついている

こんな感じで`input`タグと`span`タグを兄弟要素にするとバリデーションメッセージが表示されますが、レイアウトがうまくできませんでした。。

```php
    {{-- 略 --}}
    <input class="form-check-input form-check-inline is-invalid" type="radio" name="category_id" id="category4" value="4" >
    <label class="form-check-label" for="category4">web基礎</label>
    {{-- バリデーションメッセージ --}}
    <span class="invalid-feedback" role="alert">
        <strong>カテゴリーは必ず指定してください。</strong>
    </span>
```

### 補足

実際のコードはLaravelで

- `@error`ディレクティブでバリデーションメッセージの有無を判定
- ラジオボタンの選択肢データをDBにマスターデータとして保持して取得する（カテゴリーの追加を楽にするため）

をしているため実際のコードは以下の通りです。

**Viewファイル**

```php:create.blade.php
{{-- 略 --}}
<div class="form-group mb-4">
    <p for="exampleInputPassword1">カテゴリー<span class="text-danger">(※)</span></p>
    {{-- ラジオボタン  --}}
    @foreach ($categoryForRadioButton as $index => $categoryName)
        <div class="form-check form-check-inline @error('category_id') is-invalid @enderror">
            <input class="form-check-input form-check-inline @error('category_id') is-invalid @enderror" type="radio" name="category_id" id="category{{ $index }}" value="{{ $index }}" {{ old('category_id') == $index ? 'checked': '' }}>
            <label class="form-check-label" for="category{{ $index }}">{{ $categoryName }}</label>
        </div>                        
    @endforeach
    {{-- バリデーションメッセージ --}}
    @error('category_id')
        <span class="invalid-feedback" role="alert">
            <strong>{{ $message }}</strong>
        </span>
    @enderror
</div>
{{-- 略 --}}
```

**ラジオボタンの選択肢取得用**

```php:Category.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Category extends Model
{
    /**
     * ラジオボタン用の配列データを作成
     *
     * @return array
     */
    public function getAll(): array
    {
        $categoryForRadioButton = [];

        $allCategories = $this->all();
        foreach ($allCategories as $index => $category) {
            $categoryForRadioButton[$index + 1] = $category->name;
        }
        return $categoryForRadioButton;
    }
}
```

## 参考

- [Bootstrap-Custom styles](https://getbootstrap.jp/docs/4.1/components/forms/#custom-styles)
- [Bootstrap 4 のradioやcheckboxでスマートにエラーメッセージを表示するには](https://www.e-pokke.com/blog/bootstrap4-invalid-feedback.html)
- [Readouble-Laravel 6.x バリデーション（バリデーションエラー表示）](https://readouble.com/laravel/6.x/ja/validation.html#quick-displaying-the-validation-errors)