【Laravel】バリデーションのrequiredとビューの必須マークを連動させたい

## 概要

こういう必須マーク、よく付け忘れや消し忘れが目立つ。
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290284/112ce6bf-e7d9-178a-1b69-3183fb40f0fa.png)
なのでバリデーションルールのrequiredに沿ってviewに表示されるような仕組みを考えてみました。

## 考え方

* コントローラーでバリデーション配列を呼び出し、ビューにセットする
* ビューの入力フォームで入力フォームの名前をセットし、入力フォームの該当のバリデーション配列内にrequiredがあれば必須マークを付ける

## 実装例

* コントローラー
* バリデーション
* ビュー(入力フォーム部分)
* ビュー(必須マーク部分)

を記載します。
やりたいことが何となく伝わればよいかなということで、バリデーションルールとかビューの入力項目1個1個の中身は適当です。

### コントローラー

<details><summary>サンプルコード</summary><div>

```UserController.php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Http\Requests\UserCreateRequest;

class UserController extends Controller
{
    public function create()
    {
        $validation = new UserCreateRequest();
        return view('user.create', [
            'rules' => $validation->rules(),
        ]);
    }
}
```
</div></details>

### バリデーション

<details><summary>サンプルコード</summary><div>

```UserCreateRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UserCreateRequest 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 [
            'name' => 'required|max:40',
            'name_kana' => 'required|max:40|regex:/^[ァ-ヶー]+$/u',
            'post_code' => 'nullable|numeric|digits:7',
            'pref_code' => 'nullable',
            'address1' => 'nullable|max:80',
            'address2' => 'nullable|max:80',
            'email' => 'required|email|confirmed|max:255',
            'login_id' => 'required|min:8|max:32|unique:users,login_id',
            'password' => 'required|confirmed|min:8|max:32',
        ];
    }
}
```
</div></details>

### ビュー(入力フォーム部分)

<details><summary>サンプルコード</summary><div>

```php5:create.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>ユーザー登録</title>
    <meta name="robots" content="noindex">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.1/css/bootstrap.min.css"
          integrity="sha384-VCmXjywReHh4PwowAiWNagnWcLhlEJLA5buUprzK8rxFgeH0kww/aWY76TfkUoSX" crossorigin="anonymous">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
            integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
            crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"
            integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV"
            crossorigin="anonymous"></script>
</head>
<body class="bg-light environment_image">
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
    <a class="navbar-brand" href="/">ユーザー管理画面</a>
</nav>
<div class="container mb-5">
    <div class="card">
        <h5 class="card-header">ユーザー登録</h5>
        <div class="card-body">
            <div class="mb-3 collapse show">
                @if (!empty(session('messages')))
                    <div class="alert alert-success" role="alert">
                        @foreach (session('messages') as $message)
                            {{ $message }}<br>
                        @endforeach
                    </div>
                @endif
                <form action="" method="POST" id="input_form">
                    @csrf

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="last_name">名前
                                @include('common.required',['rules' => $rules['name'] ?? ''])
                            </label>
                            <input type="text" name="name" id="name" class="form-control">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="name_kana">名前（フリガナ）
                                @include('common.required',['rules' => $rules['name_kana'] ?? ''])
                            </label>
                            <input type="text" name="name_kana" id="name_kana" class="form-control">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="post_code">郵便番号
                                @include('common.required',['rules' => $rules['post_code'] ?? ''])
                            </label>
                            <input type="text" name="post_code" id="post_code"
                                   class="form-control"
                                   placeholder="9999999"
                                   value="">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="pref_code">都道府県
                                @include('common.required',['rules' => $rules['pref_code'] ?? ''])
                            </label>
                            <select id="pref_code" class="form-control" name="pref_code">
                                <option value="">選択してください</option>
                                <option value="13">東京都</option>
                            </select>
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="address1">市区町村
                                @include('common.required',['rules' => $rules['address1'] ?? ''])
                            </label>
                            <input type="text" name="address1" id="address1"
                                   class="form-control" placeholder="〇〇市××町"
                                   value="">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="address2">番地、マンション名、部屋番号
                                @include('common.required',['rules' => $rules['address2'] ?? ''])
                            </label>
                            <input type="text" name="address2" id="address2"
                                   class="form-control" placeholder="１－２－３"
                                   value="">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="email">メールアドレス
                                @include('common.required',['rules' => $rules['email'] ?? ''])
                            </label>
                            <input type="text" name="email" id="email"
                                   class="form-control"
                                   placeholder="メールアドレスを入力してください"
                                   value="">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="email_confirmation">メールアドレス（確認）
                                @include('common.required',['rules' => $rules['email_confirmation'] ?? ''])
                            </label>
                            <input type="text" name="email_confirmation" id="email_confirmation"
                                   class="form-control"
                                   placeholder="メールアドレスを入力してください">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="login_id">ログインID
                                @include('common.required',['rules' => $rules['login_id'] ?? ''])
                            </label>
                            <input type="text" name="login_id" id="login_id"
                                   class="form-control"
                                   placeholder="英数字で入力してください"
                                   value="">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="password">パスワード
                                @include('common.required',['rules' => $rules['password'] ?? ''])
                            </label>
                            <input type="password" name="password" id="password"
                                   class="form-control"
                                   placeholder="パスワードを入力してください" value="">
                        </div>
                    </div>

                    <div class="form-row">
                        <div class="form-group col-md-6">
                            <label for="password_confirmation">パスワード（確認）
                                @include('common.required',['rules' => $rules['password_confirmation'] ?? ''])
                            </label>
                            <input type="password" name="password_confirmation" id="password_confirmation"
                                   class="form-control"
                                   placeholder="パスワードを入力してください" value="">
                        </div>
                    </div>

                    <button type="button" class="btn btn-primary m-2 disabled_button">登録</button>
                </form>
            </div>
        </div>
    </div>
</div>
</body>
</html>
```
</div></details>

### ビュー(必須マーク部分)

<details><summary>サンプルコード</summary><div>

```php5:required.blade.php
@php
    // バリデーションルールが配列の形では無い場合、配列にする
    if(isset($rules) && is_array($rules) === false) {
        $rules = explode('|', $rules);
    }

    // 必須表示フラグを初期化
    $is_required = false;

    // バリデーションルール配列の中にrequiredがあれば必須マークを使用する
    if(isset($rules) && is_array($rules) && in_array('required', $rules, true)) {
        $is_required = true;
    }
@endphp

@if ($is_required)
    <span class="badge badge-danger">必須</span>
@endif
```
</div></details>

## あとがき

includeが多くなったりin_arrayを使用してたりするので、フロント側の表示など速度を求められる部分には不向きかなと思います。
入力項目が多くなりがちな管理画面とかには良いかもです。