Laravel入力フォームで複数のデータをまとめてJSONデータで送信し、フォームリクエストで処理する方法

業務でLaravelを使って開発しています。少し工夫が必要な入力フォームを作る必要がったので、メモ代わりに記録を残しておきます。

Laravelは、便利なフォームリクエストを使用してフォームのデータをバリデーションおよび処理することができます。このチュートリアルでは、入力フォームからJSONデータを送信し、フォームリクエストを使用してそのデータをバリデーション・処理する方法を紹介します。

検証環境
Laravel5.8

## やりたいこと
セレクトボックスで値を選択したら、その値に付随するほかの項目も一緒にリクエストとして送信したい。
具体的には、教科という科目があったとき、国語、英語、数学などを要素に持つ、セレクトボックスがあります。以下のようなコードは、よく見かけます。
```html
<select name="subject">
    <option value="英語">英語</option>
    <option value="理科">理科</option>
    <option value="国語">国語</option>
</select>
```
このとき、国語を選んだとき、担任講師(teacher)と難易度(difficulty)も一緒にリクエストとして送信したいとしたらどうでしょうか？
国語が選択されたときは、以下のような値を送信したいです。
```
{ "subject": "国語", "teacter" : "田中", "difficulty" : "4"}
```

## 1. フォームの作成

まず、HTMLフォームを作成します。ここでは、科目の選択肢を含むセレクトボックスを用意します。各選択肢にはJSON形式でデータを格納します。

```html
<form action="/process-form" method="POST">
    @csrf
    <select name="subject">
        <option value='{"name": "英語", "teacher": "佐藤", "difficulty": "3"}'>英語</option>
        <option value='{"name": "理科", "teacher": "内藤", "difficulty": "8"}'>理科</option>
        <option value='{"name": "国語", "teacher": "田中", "difficulty": "4"}'>国語</option>
    </select>
    <button type="submit">登録</button>
</form>
```

## 2. フォームリクエストの作成

フォームデータをバリデーションするために、フォームリクエストを作成します。

SubjectRequest クラスに、以下のコードを追加します。
```php

use Illuminate\Foundation\Http\FormRequest;

class SubjectRequest extends FormRequest
{
     public function rules()
     {
        return [
            'subject_info' => 'json',
        ];
     }

    public function withValidator($validator)
    {
        $subjectData = json_decode($this->input('subject_info'), true);

        $this->validate($subjectData, $dataRules);

        return $subjectData;

         $validator->after(function ($validator) {
            if (!$this->has('subject_info')) {
                return;
            }

            $subjectInfo = json_decode($this->input('subject_info'), true);

            if (!$subjectInfo || !is_array($carInfos)) {
                $validator->errors()->add('subject_info', 'Invalid JSON data');
                return;
            }

            // JSONデータ内の各要素をフォームに追加
            $this->merge([
                'subject' => $subjectInfo['subject'],
                'teacher' => $subjectInfo['teacher'],
                'difficulty' => $subjectInfo['difficulty']
              ]);

            // バリデーションルールを追加したフィールドに適用
            $validator->addRules([
                'subject' => 'required|string|max:10',
                'teacher' => 'required|string|max:20',
                'difficulty' => 'required|int',
            ]);
        });
    }
}
```
## 3. コントローラの処理

コントローラ内で、フォームリクエストを受け取り、JSONデータを処理してDBに保存します。

```php

use Illuminate\Http\Request;
use App\Http\Requests\SubjectRequest;
use App\Models\Subject; // 適切なモデル名を指定

public function processForm(SubjectRequest $request)
{
    $params = $request->only['subject','teacher','difficulty'];
    // 省略
}
```
## まとめ

Laravelを使用して入力フォームからJSONデータを送信し、フォームリクエストでバリデーション・処理する方法を紹介しました。わざわざ一つのメニューのvalue属性にJSONを入れる必要がないことに気がつきました。
<input>タグでhidden属性にすればよいですね。ただ、<option>タグの中には、<input>タグを入れることが出来なかったりと、配置場所には注意が必要です。また、選択肢が選ばれたとき、inputタグのvalue属性を変更しなければいけません。ですが、わざわざJSON形式でセットするより、下記のコードのほうが分かりやすいかもしれません。

```
<form action="/process-form" method="POST">
    @csrf
    <select name="subject">
        <option value='英語'>英語</option>
        <option value='理科'>理科</option>
        <option value='国語'>国語</option>
    </select>
    <input type="hidden" name="teacher" value="">
    <input type="hidden" name="difficulty" value="">
    <button type="submit">登録</button>
</form>
```