Laravel で他のフィールド名を受け取って、配列にも対応したカスタムバリデーションルールを作る

## 経緯
Laravel のバリデーションルールの [same](https://laravel.com/docs/6.x/validation#rule-same) 等は他のフィールド名を受け取る事ができ、配列にも対応している。
このようなカスタムバリデーションルールを作ろうと思ったがなかなか情報が見つからずに苦労したのでまとめる。

あるデータ内の2つの値の合計が41になる事を検証したい事がよくあると思うので[^1]本記事では例として実装する。
[^1]: ない

## 配列に対応していない版
`AppServiceProvider::boot` 内で `Validator` ファサードの `extend` メソッドを使ってルールを登録する。

```php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Validator;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Validator::extend('sum41', function ($attribute, $value, $parameters, $validator) {
            return $value + ($validator->getData())[$parameters[0]] === 41;
        });
    }
}
```

`rule:param1[,...]` のようにルールに渡した引数は、`extend` メソッドのクロージャの第3引数で配列として受け取れる。

第4引数は `\Illuminate\Validation\Validator` のインスタンスで、`getData` メソッドを使えば全体のデータが取得できる。

よって `param1` に他のフィールドのキーを指定しているなら

```php
($validator->getData())[$parameters[0]]
```

でそのフィールドの値が取得できる。

実際にルールを使ってみる。

```php
>>> $data = ['data1' => 10, 'data2' => 31];
>>> $rules = ['data1' => 'required', 'data2' => 'sum41:data1'];
>>> Validator::make($data, $rules)->validate()
=> [
     "data1" => 10,
     "data2" => 31,
   ]
>>> $data2 = ['data1' => 10, 'data2' => 30];
>>> Validator::make($data2, $rules)->validate()
Illuminate/Validation/ValidationException with message 'The given data was invalid.'
```

データが一件の時は問題なく動いているように見えるが、配列に対して使おうとすると `*.data1` をそのままキーとして認識するため `Notice:  Undefined index` が発生する。

```php
>>> $data = [['data1' => 10, 'data2' => 31], ['data1' => 20, 'data2' => 21]];
>>> $rules = ['*.data1' => 'required', '*.data2' => 'sum41:*.data1'];
>>> Validator::make($data, $rules)->validate()
PHP Notice:  Undefined index: *.data1 in /***/app/Providers/AppServiceProvider.php on line 14
```

## 配列に対応
調べた所、パラメータの `*` を配列のインデックスに変換するためには `extend` の代わりに `extendDependent` メソッドを使ってルールを登録すれば良い事が分かった。（この情報がなかなか見つからなかった）

これだけで `$parameters` 配列に自動的に `0.data1` のような値が入ってくれる。

また、 `$validator->getData()` は

```
array:2 [
  0 => array:2 [
    "data1" => 10
    "data2" => 31
  ]
  1 => array:2 [
    "data1" => 20
    "data2" => 21
  ]
]
```

このような構造になっているので

```php
Arr::get($validator->getData(), $parameters[0])
```

で目的の値が取得できる。

最終的に以下のようになった。

```php
<?php

namespace App\Providers;

use Arr;
use Illuminate\Support\ServiceProvider;
use Validator;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Validator::extendDependent('sum41', function ($attribute, $value, $parameters, $validator) {
            return $value + Arr::get($validator->getData(), $parameters[0]) === 41;
        });
    }
}
```

```php
>>> $data = [['data1' => 10, 'data2' => 31], ['data1' => 20, 'data2' => 21]];
>>> $rules = ['*.data1' => 'required', '*.data2' => 'sum41:*.data1'];
>>> Validator::make($data, $rules)->validate()
=> [
     [
       "data1" => 10,
       "data2" => 31,
     ],
     [
       "data1" => 20,
       "data2" => 21,
     ],
   ]
```

## 別の方法
一度しか使わないルールを検証時にその場で記述したい場合は、`\Illuminate\Validation\Validator::addDependentExtension` でもルールの登録ができる。

```php
$validator = Validator::make($data, $rules);
$validator->addDependentExtension('sum41', function($attribute, $value, $parameters, $validator) {
    return $value + Arr::get($validator->getData(), $parameters[0]) === 41;
});
$validator->validate();
```