今回はLaravelのバリデーションのエラーメッセージの話です。
Laravel で確認していますがでも同じだと思います。
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()
を書くだけで、エラーの場合は自動でエラーメッセージを含むレスポンスを作成してくれますが(フォームリクエストも然り)、何かしら複雑な事情があって手動でレスポンスを作りたい場合も、ありますよね
その場合は、
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)
することで、自由にメッセージを追加することができます。
ネストが深いバリデーションエラーメッセージ
ここからが本題です
上で見たように、エラーメッセージは基本的に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"
}
*/
何故か、「この」と「エラーメッセージは」の間に配列が挟まっていますね
withMessages()
でも同様になります。
どういうことでしょうか。
$validator->errors()->add()
のadd()
はMessageBag
のadd()
です。
それを調べてみます。
/**
* 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()
では、
/**
* 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()
を使いましょう。
これなら余計な配列は作られません。
$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()
を使うことでエラーメッセージの構造を崩すことなく、深い階層構造を持つエラーメッセージを返すことができました