Laravelでバリデーションに引っかかった項目を取得する方法


# 概要

laravelのバリデーションに引っかかった項目を取得する方法を書こうと思う。

この記事を読むと、バリデーションに引っかかったもののみ、nullとして扱いたいといったようなユースケースに対応できるようになる。

# 動作環境
```
laravel: 10.47
php: 8.2
```

# まず結論から

$validation->invalid();を使用することで、バリデーションに引っかかった項目を取得することができる。

```php
/**
 * Store a new blog post.
 */
public function store(StorePostRequest $request): RedirectResponse
{
    $rules = $this->getNullInsertIfValidationFailRule();
    $validation = Validator::make($request->all(), $rules);

    // ここでバリデーションに引っかかったフィールドを取得することができる
    $failFields = $validation->invalid();

    
    // keyが$failFieldsに存在する場合は、null。そうでない場合は$requestから取得する
    $name = array_key_exists('name', $failFields) ? null : $request->input('name');
    $email = array_key_exists('email', $failFields) ? null : $request->input('email');
 
    // Store the blog post...
 
    return redirect('/posts');
}


private function getNullInsertIfValidationFailRule(): array
{
    return [
        'name' => ['present', 'nullable', 'string', 'max:50'],
        'email' => ['present', 'nullable', 'string', 'max:255'],
    ];
}

```

# 経緯
laravelのドキュメントを読んでいると、validateされた項目を取得するコードを見つけた。
この処理を詳しく見ていく。

```php
/**
 * Store a new blog post.
 */
public function store(StorePostRequest $request): RedirectResponse
{
    // The incoming request is valid...
 
    // Retrieve the validated input data...
    $validated = $request->validated();
 
    // Retrieve a portion of the validated input data...
    $validated = $request->safe()->only(['name', 'email']);
    $validated = $request->safe()->except(['name', 'email']);
 
    // Store the blog post...
 
    return redirect('/posts');
}
```
[Validation - Laravel 10.x - The PHP Framework For Web Artisans](https://laravel.com/docs/10.x/validation#creating-form-requests)から引用


### `$request->validated()`を中身を読む

`$this->invalid()`が真である場合、exceptionを投げるようなコードが書かれている。
どうやら、invalid()がバリデーションルールにリクエスト値が適合しているか判定しているメソッドのようだということがわかる。

```php
public function validated()
    {
        throw_if($this->invalid(), $this->exception, $this);

        $results = [];

        $missingValue = new stdClass;

        foreach ($this->getRules() as $key => $rules) {
            $value = data_get($this->getData(), $key, $missingValue);

            if ($this->excludeUnvalidatedArrayKeys &&
                in_array('array', $rules) &&
                $value !== null &&
                ! empty(preg_grep('/^'.preg_quote($key, '/').'\.+/', array_keys($this->getRules())))) {
                continue;
            }

            if ($value !== $missingValue) {
                Arr::set($results, $key, $value);
            }
        }

        return $this->replacePlaceholders($results);
    }

```

### `$this->invalid()`の中身を読む

コードを読むと失敗した項目をkeyにした配列を返してくれることがわかる。
メソッドもpublicなので、こちらをそのまま呼べば、バリデーションに引っかかった項目を取得できそうだ。

```php
/**
 * Returns the data which was invalid.
 *
 * @return array
 */
public function invalid()
{
    if (! $this->messages) {
        $this->passes();
    }

    $invalid = array_intersect_key(
        $this->data, $this->attributesThatHaveMessages()
    );

    $result = [];

    $failed = Arr::only(Arr::dot($invalid), array_keys($this->failed()));

    foreach ($failed as $key => $failure) {
        Arr::set($result, $key, $failure);
    }

    return $result;
}
```

# まとめ
なかなか使い所が難しいユースケースであるが、バリデーションに引っかかった場合の項目を取得するといったことができることがわかった。

私が見つけられなかっただけかもしれないが、laravelドキュメントに書かれていない情報と思ったので今回書いてみました。

この記事が役に立ったと思っていただけたのであれば、いいね下さい！！