Laravelでネストが深いバリデーションエラーメッセージを返す

今回はLaravelのバリデーションのエラーメッセージの話です。
Laravel :seven:で確認していますが:six:でも同じだと思います。

https://laravel.com/docs/7.x/validation
https://readouble.com/laravel/7.x/ja/validation.html

# 通常のバリデーション

Laravelでは、普通に作ると以下のような構造で`$errors`にエラーメッセージが保存され、それをビューで使用します。

```php
// 例えば、

$request->validate([
    'title' => 'required|string|max:100',
    'body'  => 'required|string|max:255',
]);

// というバリデーションだと、$errorsには、

$errors = [
    'title' => ['The title field is required.'],
    'body' => ['The body field is required.']
]

// それぞれのフィールドに対してメッセージの配列が作られます。
```

通常は、ビューでこれをそのまま使えば問題ありません。
上記は`$request->validate()`を書くだけで、エラーの場合は自動でエラーメッセージを含むレスポンスを作成してくれますが(フォームリクエストも然り)、何かしら複雑な事情があって手動でレスポンスを作りたい場合も、ありますよね:wink:
その場合は、

```php
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\Validator;

...

$validator = Validator::make([], []);
$validator->errors()->add('title', 'タイトルのエラーです。');
throw new ValidationException($validator);

// もしくは

throw ValidationException::withMessages(['title' => 'タイトルのエラーです。']);

...
```

こうやって`$validator->errors()->add($key, $message)`することで、自由にメッセージを追加することができます。

# ネストが深いバリデーションエラーメッセージ

ここからが本題です:bangbang:
上で見たように、エラーメッセージは基本的に2次元配列です。
しかし、エラーメッセージの構造をもっと階層的にしたい場合もあります。

```php
// 例えば、

[
    'この' => [
        'エラーメッセージは' => [
            'とても' => [
                '深いです' => [
                    'とてもとても深いエラーメッセージ'
                ]
            ]
        ]
    ]
]
```

これを上記の`add()`または`withMessages()`でやってもうまくいきません。
ダンプして確認してみます。

```php
$errors = [
    'この' => [
        'エラーメッセージは' => [
            'とても' => [
                '深いです' => [
                    'とてもとても深いエラーメッセージ'
                ]
            ]
        ]
    ]
];
$validator = Validator::make([], []);
$validator->errors()->add('この', $errors['この']);
dd($validator->errors());

/**
Illuminate\Support\MessageBag {#263 ▼
  #messages: array:1 [▼
    "この" => array:1 [▼
      0 => array:1 [▼
        "エラーメッセージは" => array:1 [▼
          "とても" => array:1 [▼
            "深いです" => array:1 [▼
              0 => "とてもとても深いエラーメッセージ"
            ]
          ]
        ]
      ]
    ]
  ]
  #format: ":message"
}
*/
```

何故か、「この」と「エラーメッセージは」の間に配列が挟まっていますね:thinking:
`withMessages()`でも同様になります。
どういうことでしょうか。
`$validator->errors()->add()`の`add()`は`MessageBag`の`add()`です。
それを調べてみます。

```vendor/laravel/framework/src/Illuminate/Support/MessageBag.php

    /**
     * Add a message to the message bag.
     *
     * @param  string  $key
     * @param  string  $message
     * @return $this
     */
    public function add($key, $message)
    {
        if ($this->isUnique($key, $message)) {
            $this->messages[$key][] = $message;
        }

        return $this;
    }

```

`$this->messages[$key] = $message;`ではなくて、`$key`で配列を作って、その中に`$message`を入れています。
だから配列が挟まっていたのです。
`ValidationException::withMessages()`では、

```vendor/laravel/framework/src/Illuminate/Validation/ValidationException.php

    /**
     * Create a new validation exception from a plain array of messages.
     *
     * @param  array  $messages
     * @return static
     */
    public static function withMessages(array $messages)
    {
        return new static(tap(ValidatorFacade::make([], []), function ($validator) use ($messages) {
            foreach ($messages as $key => $value) {
                foreach (Arr::wrap($value) as $message) {
                    $validator->errors()->add($key, $message);
                }
            }
        }));
    }

```

こちらでも、`MessageBag`の`add()`を使っています。
従って同様に配列が差し込まれます。
どうしたらいいのでしょうか。

# 解決策はmerge()

`merge()`を使いましょう。
これなら余計な配列は作られません。

```php
$errors = [
    'この' => [
        'エラーメッセージは' => [
            'とても' => [
                '深いです' => [
                    'とてもとても深いエラーメッセージ'
                ]
            ]
        ]
    ]
];
$validator = Validator::make([], []);
$validator->errors()->merge($errors);
dd($validator->errors());
throw new ValidationException($validator);

/**
Illuminate\Support\MessageBag {#263 ▼
  #messages: array:1 [▼
    "この" => array:1 [▼
      "エラーメッセージは" => array:1 [▼
        "とても" => array:1 [▼
          "深いです" => array:1 [▼
            0 => "とてもとても深いエラーメッセージ"
          ]
        ]
      ]
    ]
  ]
  #format: ":message"
}
*/
```

`merge()`を使うことでエラーメッセージの構造を崩すことなく、深い階層構造を持つエラーメッセージを返すことができました:beer: