LaravelのFormRequestクラス機能まとめ

# 概要
LaravelのFormRequestクラスの使い方と機能のまとめです．

# 環境
Laravel 5.5

# 準備
まずは次のコマンドでFormRequestクラスを生成します．

```console
artisan make:request TestRequest
```

ファイルは，app/Http/Requests/TestRequest.php に作成されます．作成されたファイルで

```php:app/Http/Requests/TestRequest.php
public function authorize()
{
    return true; // 元はfalse
}
```

に変更することで使用できます．

使い方は，使用するコントローラなどで

```php:ExampleController.php
use App/Http/Requests/TestRequest;

class ExampleController extends Controller
{
    public function testMethod(TestRequest $request) {
        /* 処理 */
    }
}
```

とすることで使用できます．

# 機能
#### バリデーション
ruleメソッドに連想配列・文字列で記述します．

```php:app/Http/Requests/TestRequest.php
public function rules()
{
    return [
        'price' => ['required' , 'integer' , 'min:1'],
        'discount' => 'required|integer',
    ];
}
```

このように，バリデーションルールを配列または文字列で指定できますが，配列で指定することをおすすめします．理由は後々の拡張性や可読性，一部ルールでは配列での指定が必須なので後々のことを考えると配列を使用したほうが良いからです．

使えるバリデーションルールは[ドキュメント](https://readouble.com/laravel/5.5/ja/validation.html)にあります．

### 自分で作成したバリデーションを使う
Laravelでは用意してあるバリデーションルールを組み合わせれば，大抵のバリデーションができますが，それでも特殊なバリデーションを行いたいことが多々あります．その場合は

```php:app/Http/Requests/TestRequest.php
public function withValidator($validator) {
    /* ここにバリデーションを書く */
}
```

を追加します．[ドキュメント](https://readouble.com/laravel/5.5/ja/validation.html)によれば
「このメソッドは完全に構築されたバリデータを受け取るため、バリデーションルールが実際に評価される前に、バリデータのどんなメソッドも呼び出すことができます。」
とあり，このメソッドではruleで指定したバリデーションルールが適用される前のバリデータにアクセスできます．

使い方は，例えば「割引額(discount)が支払金額(price)よりも小さい」ことをバリデートする場合は，

```php:app/Http/Requests/TestRequest.php
public function withValidator($validator) {
    $validator->after(function ($validator) {
        if($this->filled(['discount','price'])) {
            if($this->input('discount') >= $this->input('price')) {
                $validator->errors()->add('割引額', '合計金額より小さい額を指定してください．');
            }
        }
    });
}
```

とします．ルールメソッドとは異なり，直接メッセージや項目名を指定します．もし，``rules``で指定したチェックを通過した場合のみ追加チェックを行いたい場合は

```php
public function withValidator($validator) {
    if ($validator->fails()) return;
}
```

とすることで分岐することができます．


### エラーメッセージの変更
エラー時の各項目のメッセージを

```php:app/Http/Requests/TestRequest.php
public function messages()
{
    return [
        'discount.integer' => '整数で指定してください．',
        'price.integer' => '整数で指定してくださいませ．',
    ];
}
```

で変更できます．キーは「項目名 . ルール」となっています．


### エラーメッセージの項目名の変更
ルールメソッドで指定したバリデーションで，エラーが返ってきたときにのメッセージの項目名を

```php:app/Http/Requests/TestRequest.php
public function attributes()
{
    return [
        'discount' => '割引額',
        'price' => '合計金額',
    ];
}
```

で変更できます．

### バリデーションデータの追加
apiなどではjson形式でデータを送信することがありますが，このようなときにデータを展開してバリデーションにかけるには，

```php:app/Http/Requests/TestRequest.php
//オーバーライド
protected function validationData()
{
    $data = json_decode($this->input('json'), true);
    return $this->all() + ['data' => $data];
}
```

とします．バリデーションは

```php:app/Http/Requests/TestRequest.php
public function rules()
{
    return [
        'data.price' => ['required' , 'integer' , 'min:1'],
        'data.discount' => 'required|integer',
    ];
}
```

でかけれます．ネストにしたくない場合は，```'data' => $data``` を```$data```にしたり

```php
protected function validationData()
{
    $inputs = $this->all();
    $inputs['json'] = json_decode($this->input('json'), true);
    return $inputs;
}
```

として，入力を上書きします．

これはバリデーションがかけられるデータが```validationData```メソッドの返り値に対してかけられるため，元のデータにマージしてデータを追加することでバリデーションがかけられるデータを追加しています．したがってjsonにかかわらず別のデータを追加することもできます．例えば，ルートパラメータを追加する場合は

```php
protected function validationData()
{
    return $this->all() + $this->route()->parameters();
}
```

とします．

### 入力値の整形
例えば，日付の入力フォームが年・月・日で別れている場合など，それらを結合したデータを追加することができます．

```php
protected function passedValidation()
{
    $date = Date::createMidnightDate($this->input('year'), $this->input('month'), $this->input('day'));

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

また，年月日が一つのフォームになっていたとしても，そのままでは文字列型なので，

```php
protected function passedValidation()
{
    $this->merge([
        'date' => Date::parse($this->input('date')),
    ]);
}
```

とすることで，コントローラ側で入力値をCarbon型で受け取ることができます．

```php
$request->input('date');  // Carbon
```

### メソッドの追加
FormRequestクラスに追加したメソッドは，呼び出すことができます．例えばCSVファイルを受け取る場合，

```php:app/Http/Requests/TestRequest.php
public function getCsvData()
{
    $csvFile = $this->file('csv');
    $csvData = array();
    /* 受け取ったCSVファイルをからデータを取り出す */
    return $csvData;
}
```

のようにメソッドを追加すれば，コントローラ側で

```php:ExampleController.php
class ExampleController extends Controller
{
    public function testMethod(TestRequest $request) {
        $csvData = $request->getCsvData();
    }
}
```

とすることで，CSVファイルのデータを受け取れます．他にも，json形式でデータを受け取る場合では

```php:app/Http/Requests/TestRequest.php
public function decodedData()
{
    $data = $this->all();
    $data['json'] = json_decode($data['json'], true);

    return $data;
}
```

としてしまえば，コントローラ側で```json_decode```をかける必要がなくなります．前段のように

```php
protected function passedValidation()
{
    $this->merge([
        'json' => json_decode($this->input('json'), true),
    ]);
}
```

としても良いと思います．

### 入力値の取得
FormRequestから入力値は以下のように取り出すことができます．

```php
// すべて取り出す
$request->all();
$request->input();

// 一つだけ取り出す
$request->input('data', 'デフォルト値');
$request->data;

// 複数取り出す
$request->only(['data1', 'data2']);

// 指定したもの以外を取り出す
$request->except(['data3']);
```

これを使うことで，モデルの更新などを

```php
$user = User::findOrNew($request->id);
$user->fill($request->except('id'));
$user->save();
```

このように書くことができます．

# まとめ
FormRequestクラスを用いることで，バリデーションとコントローラを分離することができ，可読性や機能管理が容易になります．また，送信データに対する一次加工(```json_decode```など)もこちらに含めてしまえば，コントローラ側でデータを加工する手間が省けます．

# 参考
- [ドキュメント](https://readouble.com/laravel/5.5/ja/validation.html)
- [Laravel5.7 : FormRequestクラスを使ってValidation(MyMemo)](https://qiita.com/daisu_yamazaki/items/e44d4b744d9d5f9bc8b3)