Laravel 10時代のバリデーション拡張


[Laravel の Validation を正しく拡張する](https://qiita.com/moobay9/items/f1cdd3c8f995fdcf0963)という手前味噌の記事はそこそこ読まれておりますが、Laravelのバージョン５の時代のやり方もわるくはないですが、さすがに10時代に合わせてルールの追加はお作法に乗っ取って行いたい。と思うようになりまして。

ドキュメントを読むと、[カスタムバリデーションルール](https://laravel.com/docs/10.x/validation#custom-validation-rules)というセクションがあり、そこで解説されています。詳細はそちらに譲るとして、そっちだけではちと情報が不足しています。



## まずはお作法通りにやると？

```
php artisan make:rule Uppercase
```

とやると、`app/Rules/Uppercase` というファイルができます。

```php
<?php
 
namespace App\Rules;
 
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
 
class Uppercase implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (strtoupper($value) !== $value) {
            $fail('The :attribute must be uppercase.');
        }
    }
}

```


ここまでは何の難しさもなく、へえそうやってできるんだぁ、という感じですね。
いつも通り FormRequest内で利用したい場合、ルールを頭で宣言して、`rules()` の中ではインスタンス化する書き方を行います。

```php:App\Http\Requests\FormRequest;

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use App\Rules\{Uppercase}; // 複数選択する場合は波括弧で囲めば一行で書ける

class FormRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'name' => ['required', new Uppercase],
        ];
    }

    public function attributes(): array
    {
        return [
            'name' => 'お名前',
        ];
    }
}
```

ここまでは何の難しさもありません。Uppercase をひらがなとかカタカナにすればそれで終わりです。


## エラーのメッセージは lang 以下のファイルを利用したい

普段は `lang/en/validation.php` とか en を ja にしたファイルを利用して文言を調整している事が多いかと思います。
なので、以下のように validation ファイルの中にエラーメッセージを追加します。

```php:lang/ja/validation.php
<?php

return [
(略)
    'katakana' => ':attribute はカタカナで入力してください。',
(略)

];

```

そうすると、$fail で返すエラーメッセージに指定を行えるようになりますので、`$fail('validation.katakana')->translate();` と書けるようになります。
ここまでは公式ドキュメントにも解説してあります。


```php:App\Rules\Katakana.php
<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class Katakana implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if( ! (bool) preg_match('/^[ァ-ヾ 　〜ー−]+$/u', $value)) {
            $fail('validation.katakana')->translate();
        }
    }
}
```

## 公式ドキュメントに書かれてないけどやりたい事

例えば `between` というバリデーションルールでは、`between:1,5` のような書き方をして１から５の間というルールになるわけですが、最大値が255文字まで、という **max_length** ルールを独自に作りたいと考えた時に、じゃあ引数どうすんだ？って考えるわけですが、公式に解説は無いわけです。

なのでそれを素直に実装しますと、まず呼び出すFormRequest側の書き方としては

```php:App\Http\Requests\FormRequest
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use App\Rules\{MaxLength};

class FormRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'name' => ['required', new MaxLength(20)],
        ];
    }

    public function attributes(): array
    {
        return [
            'name' => 'お名前',
        ];
    }
}
```

という具合に `new MaxLength(20)` という書き方になるわけですね。

じゃあ素直に考えればコンストラクタで実装すればよろしい、と言えますので、

```
php artisan make:rule MaxLength
```

でルールのテンプレートを作成後、以下のように書き直します。

```php:lang/ja/validation.php
<?php

return [
(略)
    'max_length' => ':attribute は :max 文字以下で指定してください。',
(略)
];
```

```php:App\Rules\MaxLength.php
<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class MaxLength implements ValidationRule
{
    protected $max_length;

    public function __construct(string $max_length)
    {
        $this->max_length = $max_length;
    }

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if(mb_strlen($value) > $this->max_length) {
            $fail('validation.max_length')->translate(['max' => $this->max_length]);
        }
    }
}
```

大事なのは、最後の `translate()` の中が今までは空だったのを、プレースホルダーを指定しているところです。
言語ファイル側で :max というプレースホルダーを利用している場合、 translate 内できちんと指示を与えないと `名前は :max 文字以下で指定してください。` という具合にプレースホルダーがプレースされずにそのまま出てきてしまいます。



## ということで

1. php artisan make:rule でルールを作成し
2. 真偽判定を `validate()` で行い
3. エラー時にエラーメッセージを返す指定をする
4. ルールは読み込んで new しながら使う


今まで通り配列内に, `['required', 'katakana', 'max_length:255']` ていう書き方を使いたいんじゃ！！　という方は一番上の過去記事をご参照ください。