LoginSignup
15
9

More than 3 years have passed since last update.

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

Posted at

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

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

通常のバリデーション

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

// 例えば、

$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:
その場合は、

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次元配列です。
しかし、エラーメッセージの構造をもっと階層的にしたい場合もあります。

// 例えば、

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

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

$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()MessageBagadd()です。
それを調べてみます。

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);
                }
            }
        }));
    }

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

解決策はmerge()

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

$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:

15
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
9