Laravel/LumenでValidatorライブラリを作る

# はじめに

アプリケーションが大きくなってくると、複数のリポジトリにまたがるライブラリを作りたくなってきます。
それを`composer install`で入れられたらもうダブルメンテなんて気にしなくて済みますよね。
ビジネスロジックに依存したバリデータは重複するものが多いので、わりとライブラリに切り出しやすかったりします。

Laravel/Lumenにはカスタムバリデータを簡単に作る機構があるのですが、それを独立したライブラリに切り出すのはちょっとコツが必要だったのでご紹介します。

（コツは[ここから](#バリデーションメッセージをどこに定義するか)）

# カスタムバリデータを定義する（どこにでも載ってる話）

まずライブラリのソース構成はこんな感じです。Composerのパッケージ定義等は割愛します。

```
src/
 ├ Providers/
 │   └ ValidatorServiceProvider.php
 └ Validators/
     └ NumberValidator.php
```

次にカスタムバリデータクラスを作成します。
1. `Illuminate\Validation\Validator`を継承する
2. `validateXXX`という命名規則でバリデーションメソッドを定義する

```
use Illuminate\Validation\Validator;

class NumberValidator extends Validator
{
    public function validateNaturalNumber(string $attribute, $value, array $parameters, Validator $validator): bool
    {
        if ($validator->validateInteger($attribute, $value)) {
            return $value > 0;
        }
        return false;
    }
}
```

そしてバリデータをServiceProviderに登録します。

```
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\ServiceProvider;
use Relux\Validators\NumberValidator;

class ValidatorServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Validator::resolver(function ($translator, $data, $rules, $messages) {
            return new NumberValidator($translator, $data, $rules, $messages);
        });
    }
}
```

使うときはメソッド名をスネークケースにしたもので呼び出せます。

```
'required|natural_number'
```

ここまではどこにでも載っている話。

# バリデーションメッセージをどこに定義するか

通常Laravel/Lumenのアプリケーションでバリデーションメッセージを定義する場合、下記のように言語ファイルを配置することでフレームワークが言語ファイルを読みに行ってくれます。

```
app/
 └ resources/
    └ lang/
       └ en/
          └ validation.php
```

ただ今回はComposerライブラリ内なので上記のような言語ファイル配置はできません。
でも同じようなことをライブラリでも実現したいと思い調べていると、こんな記述がありました。
https://laravel.com/docs/5.7/packages#translations 

```
public function boot()
{
    $this->loadTranslationsFrom(__DIR__.'/path/to/translations', 'courier');
}
```

ServiceProviderで`loadTranslationsFrom`を呼ぶことで言語ファイルを読み出すことができ、

```
echo trans('courier::messages.welcome');
```

という風にネームスペース付きで翻訳を呼び出すことができます。

# 最終的な実装はこうなった

```
src/
 ├ Providers/
 │   └ ValidatorServiceProvider.php
 ├ Validators/
 │   └ NumberValidator.php
 └ resources/
   　　 └ lang/
    　　   └ en/
     　　     └ validation.php
```

ディレクトリ構成はLaravelと同じにしています。
`validation.php`の中身は、通常の言語ファイルと同じようにただの連想配列。

```
return [
    'natural_number' => 'The :attribute must be natural number.',
];
```

ServiceProviderで言語ファイルの読み出しと、バリデータにメッセージをセットする。

```
class ValidatorServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // 言語ファイルをロード
        $this->loadTranslationsFrom(__DIR__.'/../resources/lang', 'Relux');

        // バリデーションメッセージを取得
        $customMessages = trans('Relux::validation');
        
        Validator::resolver(function ($translator, $data, $rules, $messages) use ($customMessages) {
            $validator = new NumberValidator($translator, $data, $rules, $messages);

            // バリデータにメッセージ群をセット
            $validator->setCustomMessages($customMessages);
            return $validator;
        });
    }
}

```

`loadTranslationsFrom`はネームスペース付きで言語ファイルの内容を保持しています。
バリデータはネームスペースをもたせてバリデーションメッセージを取得しに行かないので、`setCustomMessages`メソッドでメッセージリストを設定する必要があります。

ここにまだ課題があるので、バリデータがネームスペース付きでバリデーションメッセージを取得しに行くようにカスタマイズできないか考えてみたいです。