Laravel 複雑なバリデーションでフォームリクエストクラスを使用する

# 概要
- フォーム送信されたときにバリデーションメソッドを使って各項目が適正であるかをチェックする
- Laravelでデフォルトで用意されたバリデーション内容では満足できない複雑なバリデーションを必要とするとき、コントローラへの記載ではコントローラーが肥大化し、可読性が落ちる。そこで、フォームリクエストクラスというバリデーションロジックをコントローラから分離させる。


# 内容
今回はF`FromRequest`クラスを継承した`SampleRequest`クラスを生成し、コントローラからのバリデーション分離を試みる。

# フォームリクエストクラスの生成

下記コマンドを実行

```sh
$ php artisan make:request SampleRequest
```

すると`app/Http/Request`下に`SampleRequest.php`が生成される

```php:app/Http/Request
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            //
        ];
    }
}
```

生成されたファイルを次のように編集していく

- authorize()ではtrueを返す
    - authorize()はユーザ権限や認証系のバリデーションを定義する
    - 特に必要ない場合はfalseを返す
- rules()にバリデーション内容を記載する

```php:app/Http/Request
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class SampleRequest 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|string|max:255',
          'gender' => 'required',
          'age' => 'required|integer',
          'file' => 'required|file|image|max:10000',
          'comment' => 'required',
        ];
    }
}
```

# コントローラ側の設定

フォームリクエスト使用に伴い、下記２点を追加する

- 生成した`SampleRequest`をuseする
- アクション名の引数に`SampleRequset`を渡す
    - フォーム内容がアクションへ渡される際にバリデーション処理が実行され、渡ってきた際にはバリデーション処理が完了している状態である

```php:app/Http/Controllers/SampleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests\SampleRequest;  // 追記

class ValiController extends Controller
{
  public function update(SampleValiRequest $request)  // 引数にフォームリクエスト渡す
  {
        return view('sample');
　}
}
```


# カスタムエラーメッセージ設定


- フォームごとに独自のエラーメッセージを表示したい場合、フォームリクエストクラスに追記するだけで実装できる
- messages()を作成し、その中にエラーメッセージ内容を記述する
    - **name.rule**の規則で記述する

```php:app/Http/Request
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class SampleRequest 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|string|max:255',
          'gender' => 'required',
          'age' => 'required|integer',
          'file' => 'required|file|image|max:10000',
          'comment' => 'required',
        ];
    }
    
    // 追記内容
    /**
     * エラーメッセージのカスタマイズ
     * @return array
     */
    public function messages()
    {
      return [
        'name.required' => '名前を入力してください',
        'name.string' => '名前は文字列で入力してください',
        'name.max' => '名前は255文字以内で入力してください',
        'gender.required'  => '性別を選択してください',
        'age.required'  => '年齢を選択してください',
        'age.integer'  => '年齢は数字で入力してください',
        'file.required'  => 'ファイルを選択してください',
        'file.file'  => 'ファイルのアップロードに失敗しました',
        'file.image'  => 'アップロード可能な画像は「jpg」「png」「bmp」「gif」「svg」です',
        'file.max'  => 'アップロードするファイルは10MBまでです',
        'comment.required' => 'コメントを入力してください',
      ];
    }
    
}
```

# 独自バリデーションの追加

- 上記に引き続きフォームリクエスト内に追記する形で実装
- withValidator()メソッドに処理内容を記載することでバリデーション追加をすることが可能
    - validatorの中に

```php:app/Http/Request
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class SampleRequest 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|string|max:255',
          'gender' => 'required',
          'age' => 'required|integer',
          'file' => 'required|file|image|max:10000',
          'comment' => 'required',
        ];
    }
    
    /**
     * エラーメッセージのカスタマイズ
     * @return array
     */
    public function messages()
    {
      return [
        'name.required' => '名前を入力してください',
        'name.string' => '名前は文字列で入力してください',
        'name.max' => '名前は255文字以内で入力してください',
        'gender.required'  => '性別を選択してください',
        'age.required'  => '年齢を選択してください',
        'age.integer'  => '年齢は数字で入力してください',
        'file.required'  => 'ファイルを選択してください',
        'file.file'  => 'ファイルのアップロードに失敗しました',
        'file.image'  => 'アップロード可能な画像は「jpg」「png」「bmp」「gif」「svg」です',
        'file.max'  => 'アップロードするファイルは10MBまでです',
        'comment.required' => 'コメントを入力してください',
      ];
    }
    
 
 // 追記内容
 
  /**
   * 独自処理を追加する
   * @param $validator
   */
  public function withValidator($validator)
  {
    $validator->after(function ($validator) {
      if (
        strtotime(date('H:i:s')) >= strtotime('21:00:00')
        ||
        strtotime(date('H:i:s')) <= strtotime('09:00:00')
      ) {
        $validator->errors()->add('field', '投稿できるのは９時から２１時までです。');
      }
    });
  }
    
}
```


# 参考
https://www.ritolab.com/entry/41