配列値の入力、つまり複数行に同じ入力項目を持つフォームの話です。お客様の登録などのフォームとは違って、バリデーションや画面でのエラーの表示など複雑な部分が多いです。しかし、コントローラで対応するより、FormRequestを使うとすっきりしたコードとなります。

配列値の入力フォーム

各行の入力項目が1つのケースから作成してみます。以下のような画面です。行数は固定で、最大3つまでのメールアドレスの入力が可能です。

コントローラのコードは、極力シンプルにして、投稿が成功したらバリデートした値を出力します。


namespace App\Http\Controllers;

use App\Http\Requests\EmailsRequest;
use Illuminate\Http\Request;

class FormController extends Controller
{
    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
         return view('form');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  App\Http\Requests\EmailsRequest  $request
     * @return \Illuminate\Http\Response
     */
    public function store(EmailsRequest $request)
    {
       ddd($request->validated());
    }
}

ブレードは、

...
<div class="container-fluid">
  <div class="row">
    <div class="col-md-12 col-lg-12 mt-2">
      <div class="card card-primary">

        <div class="card-header">
          <h3 class="card-title">複数行のフォーム</h3>
        </div>

        <form method="POST" action="{{ route('form.store') }}" class="form-horizontal" novalidate="">
          @csrf
          <div class="card-body">
            @error('emails')
              <div class="invalid-feedback d-block">{{ $message }}</div>
            @enderror            
            <div class="table-responsive">
              <table class="table table-bordered table-hover table-sm">
                <thead>
                  <tr>
                    <th>メールアドレス</th>
                  </tr>
                </thead>
                <tbody>
                  @for ($i = 0; $i < 3; $i++)
                  <tr>
                    <td>
                      <input class="form-control" maxlength="255" name="emails[]" type="text" value="{{ old('emails.'.$i) }}">
                      @error('emails.'.$i)
                        <span class="invalid-feedback d-block">{{ $message }}</span>
                      @enderror
                    </td>
                   </tr>
                  @endfor
                </tbody>
              </table>
            </div>
          </div>

          <div class="card-footer">
            <button class="btn btn-primary float-right mr-2" type="submit">保存</button>
          </div>
        </form>
      </div><!-- card -->
    </div>
  </div><!-- row -->
</div>
...

そして、FormRequestは、


namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class EmaisRequest extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'emails.*' => 'nullable|email|distinct', // nullableとしているのは空行を許すため
        ];
    }

    public function messages()
    {
        return [
            'emails.*.email'    => '不正なメールです',
            'emails.*.distinct' => '重複の入力があります'
        ];
    }
}

実際に入力するとエラーはこんな感じで出力されます。

そしてバリデートされた値はこんな感じです。

しかし、これではDBに保存するときにいちいちnullを外す必要ありますね。これもFormRequestで処理したいです。

入力値の加工

FormRequestで入力値を加工するには、以下のようにprepareForValidation()を追加して処理します。

...
class EmailsRequest extends FormRequest
{
    protected function prepareForValidation()
    {
        $emails = collect($this->emails)
            ->filter(function ($email) {
                return $email !== null; //ここでnull値を削除
            })->all();

        $this->merge([
            'emails' => $emails,
        ]);
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'emails'   => 'required|array', //入力を必須とする
            'emails.*' => 'nullable|email|distinct',
        ];
    }

    public function messages()
    {
        return [
            'emails.required'   => '必ず1つの入力が必要です',
            'emails.*.email'    => '不正なメールです',
            'emails.*.distinct' => '重複の入力があります'
        ];
    }
}

最低1つの入力を条件とするために、'emails' => 'required|array', rules()も追加しています。

さて、先の投稿でバリデートされたデータは、今度は以下のようにnullの値はなくなりました。

そして、何も入力がなく投稿された場合は以下のようなエラーとなります。

ちなみに、最低2つの入力を条件としたいときは、ルールを、'emails' => 'required|array|min:2', とすればよいです。

By khino