Laravel5.7でAPIのJSONエラーメッセージをカスタマイズしたい


APIのバリデーションも（フロントと同様）FormRequestで行いたい場合の対応。

## やりたいこと

* エラー時のJSONメッセージのカスタマイズ（statusとかの追加）したい
* URLパラメータ(/users/{id}/{password})もバリデーションしたい

JSONのカスタマイズのイメージは

```json
{
    "status": "NG",
    "date": "2018-10-24",
    "message": "password must be at least 4 chars。"
}
```

こな感じ。
status入れたり、エラーメッセージはバリデーション機能が返すものを利用するけどkeyは変えたいとか。


## 実装

FormRequestを利用していろいろ記述する。

### ルーティング

api.phpに下記を記述。
POST送信時にURLパラメータ(idやpassword)もValidateしたい。POSTではnameというパラメータを送るつもり。

```php:api.php
Route::post('store/{id}/{password}', 'UserController@store');
// Route::get('users/{id}/{password}', 'UserController@index');
```

>サンプルとしてはpostのみ記載するが、getの際も問題なく動作しました。

### Controller

nameを返すだけの記述。利用するFormRequestを指定。

```php:UserController
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests\IndexRequest;
use App\Http\Requests\StoreRequest;

class UserController extends Controller
{
    function store(StoreRequest $request,$id,$password)
    {
        return $request->name;
    }

    // function index(IndexRequest $request,$id,$password){
    //     return $id;
    // }
}
```

### FormRequest

下記のコマンドで生成。

```bash
php artisan make:request StoreRequest
```

そして実装。要点としては、

* rulesとかmessagesとかは普通に記述
* validationData()でパラメータを追加(id,password)
* failedValidation()でバリデーションエラー時にカスタムJSONを返すようにする

という感じ。
項目毎に分岐分各ならカスタムで実装してもいいのでは？とは思うけど、ルールが統一できるのと、記述量もやや少なくても済むのかなと。

```php:StoreRequest
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

//追加
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;

class StoreRequest extends FormRequest
{
    //とりあえず認証はtrueで
    public function authorize()
    {
        return true;
    }

    //ルールの設定
    public function rules()
    {
        return [
            'id' => 'integer',
            'password' => 'required|min:4',
            'name' => 'required'
        ];
    }

    //カスタムメッセージ
    public function messages(){
        return [
            'id.integer' => 'id must be integer.',
            'password.required' => 'password must be required.',
            'password.min' => 'password must be at least 4 chars。',
            'name.required' => 'name must be required',
        ];
    }

    //URLパラメータも処理できるように
    protected function validationData()
    {
        return array_merge($this->request->all(), [
            'id' => $this->id,
            'password' => $this->password,
        ]);
    }

    //エラー時HTMLページにリダイレクトされないようにオーバーライド
    protected function failedValidation(Validator $validator)
    {
    
        //個人的ニーズに合わせ、項目別にエラーを返す仕様
        
        //任意の値を追加
        $response['status'] = 'NG';
        $response['date'] = date('Y-m-d');
        
        //もしidにエラーがあれば
        if($validator->errors()->has('id')) {
            $response['message'] = $validator->errors()->first('id');
            throw new HttpResponseException(response()->json($response, 422));
        }

        //もしpasswordにエラーがあれば
        if($validator->errors()->has('password')) {
            $response['message'] = $validator->errors()->first('password');
            throw new HttpResponseException(response()->json($response, 422));
        }

        //もしemailにエラーがあれば
        if($validator->errors()->has('name')) {
            $response['message'] = $validator->errors()->first('name');
            throw new HttpResponseException(response()->json($response, 422));
        }

        //全体のエラーを表示
        // $response['message'] = $validator->errors()->toArray();
        // throw new HttpResponseException(
        //     response()->json($response, 422)
        // );
    }
}
```

>ちなみにget(index)の場合はnameパラメータ関連の処理を除去するだけ。

## 検証

以下で期待通りの動きをするか検証をする。

1. http://localhost:8000/1/hogehoge + name = 'foo'でpostリクエスト（正常）
2. http://localhost:8000/1/ho + name = 'foo'でポストリクエスト（passwordが4文字以下）
3. http://localhost:8000/a/hogehoge + name = 'foo'でポストリクエスト（idが数字じゃない）
4. http://localhost:8000/1/hogehoge + (nameなし）でpostリクエスト（nameなし）

以下、2の場合の結果。

```json
{
    "status": "NG",
    "date": "2018-10-24",
    "message": "password must be at least 4 chars。"
}
```