Laravel 配列のバリデーションを実装してみる

こんにちは、JeffTechです。

# 説明
LaravelでAPIを作成している際に、リクエストの中に配列が混ざっているものを必須バリデーションさせる方法を説明します。
下記のようなリクエストがJSON形式で飛んできたことを想定していきます。

```json
{
	"app": {
		"posts": [
			{
				"post": {
					"id": "3",
					"count": "10"
				}
			}
		]
	}
}
```


# この記事で実現できること
- 配列のバリデーションを実装することができる

# 開発環境
- OS: MacOs
- PHP: 7.3
- Larave: 6.2


# 実装の流れ
1. Laravelプロジェクトの作成
1. 日本語化
1. Controllerの作成＆編集
1. Requestの作成＆編集
1. テスト


# 実装



## Laravelプロジェクトの作成
まずはLaravelをインストールしちゃいます。

```bash
$ composer create-project --prefer-dist laravel/laravel api_array_validation '6.*'
```



## 日本語化
エラーメッセージを日本語で出力したいので、下記コマンドで`resources/lang/ja`ファイルを作成してください。

```bash
$ php -r "copy('https://readouble.com/laravel/6.x/ja/install-ja-lang-files.php', 'install-ja-lang.php');"
$ php -f install-ja-lang.php
$ php -r "unlink('install-ja-lang.php');"
```
デフォルトでは英語の`resources/lang/en`ファイルが使用されるようになっていますが、`config/app.php`で日本語用のファイルを使用するように設定しましょう。

```php:config:app.php
<?php

return [

    ーー省略ーー

    /*
    |--------------------------------------------------------------------------
    | Application Locale Configuration
    |--------------------------------------------------------------------------
    |
    | The application locale determines the default locale that will be used
    | by the translation service provider. You are free to set this value
    | to any of the locales which will be supported by the application.
    |
    */

    'locale' => 'ja',

    ーー省略ーー
]
```



## Controllerの作成＆編集
今回使用するコントローラの作成です。

```bash
$ php artisan make:controller PostController
```
$requestを受け取って、バリデーションが通れば成功と出力する簡単なものにします。

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

namespace App\Http\Controllers;

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

class PostController extends Controller
{
    public function index(PostRequest $request)
    {
        $request = $request->all();
        dd('成功');
    }
}
```



## Requestの作成＆編集
バリデーションを設定するためにフォームリクエストを作成していきます。

```bash
$ php artisan make:request PostRequest
```
バリデーションを実装していきます。

```php:app/Http/Requests/PostRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;

class PostRequest 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 [
            'app.posts' => 'required',
            'app.posts.*.post.id' => 'required|integer',
            'app.posts.*.post.count' => 'required|integer',
        ];
    }

    /**
     * エラーメッセージを出力
     * 
     * @return viod
     */
    protected function failedValidation(Validator $validator)
    {
        $response['errors'] = $validator->errors()->toArray();

        throw new HttpResponseException(
            response()->json($response, 422, [], JSON_UNESCAPED_UNICODE,)
        );
    }

    /**
     * エラーメッセージを変更
     * 
     * @return array
     */
    public function messages()
    {
        return [
            'application.menus.*.menu.id.required' => ':attributeは必ず指定してください。',
            'application.menus.*.menu.count.required' => ':attributeは必ず指定してください。',
        ];
    }

    /**
     * エラー用文言を設定
     * 
     * @return array
     */
    public function attributes()
    {
        return [
            'application.menus.*.menu.id' => 'application.menus.*.menu.id',
            'application.menus.*.menu.count' => 'application.menus.*.menu.count',
        ];
    }
}
```
これで実装自体は終わりです。
Response::json()の第4引数に、`JSON_UNESCAPED_UNICODE`を設定すると、unicodeで日本語をエスケープせずにそのまま返してくれます。

```
'app.posts' => 'required',
```
で配列自体にバリデーションをかけて、

```
'app.posts.*.post.id' => 'required|integer',
'app.posts.*.post.count' => 'required|integer',
```
で各要素にバリデーションをかけていきます。
エラーメッセージを出力する処理やmessagesメソッド、attributesメソッドはトレイトとかで共通化するともっと楽になりそうです。
実装したい場合は[こちら](https://qiita.com/crispypanda/items/af29d86eff90fad43b07)を御覧ください。



## テスト
今回私はinsomniaを使用してテストを行っていきます。
insomniaって何！って人はPostmanを想像してください。
insomniaめちゃ良いので、よかったら[こちら](https://insomnia.rest/)からインストールください。

`$ php artisan serve`で簡易サーバーを起動して、insomniaにてGETで`http://127.0.0.1:8000/api/validate`を叩いてみると、しっかりと下記のようなエラーメッセージが出力されると思います。

### postなしの場合

```json:リクエスト
{
	"app": {
	}
}
```

```json:レスポンス
{
  "errors": {
    "app.posts": [
      "app.postsは必ず指定してください。"
    ]
  }
}
```

### idとcountなしの場合

```json:リクエスト
{
	"app": {
		"posts": [
			{
				"post": {
				}
			}
		]
	}
}
```

```json:レスポンス
{
  "errors": {
    "app.posts.0.post.id": [
      "app.posts.0.post.idは必ず指定してください。"
    ],
    "app.posts.0.post.count": [
      "app.posts.0.post.countは必ず指定してください。"
    ]
  }
}
```

### idとcountが文字の場合

```json:リクエスト
{
	"app": {
		"posts": [
			{
				"post": {
					"id": "aaa",
					"count": "aaa"
				}
			}
		]
	}
}
```

```json:レスポンス
{
  "errors": {
    "app.posts.0.post.id": [
      "app.posts.0.post.idは整数で指定してください。"
    ],
    "app.posts.0.post.count": [
      "app.posts.0.post.countは整数で指定してください。"
    ]
  }
}
```

### 正常時
正常な値が送られている時には、コントローラで設定した`成功`と出力されます。

```json:リクエスト
{
	"app": {
		"posts": [
			{
				"post": {
					"id": "1",
					"count": "10"
				}
			}
		]
	}
}
```

```:insomnia出力
"成功"
```


# 最後に
少しでも役に立った！という時は、いいねをポチッとして
フォローしてくださると嬉しいです、、、笑

役に立たなかった時は、怒らないでコメント頂けますと幸いです笑

Twitterもやってますので、よかったら見てみてくださいね！
https://twitter.com/jefftechsaku