Laravel8, 9のパスワードバリデーションとその日本語化

# Laravelのバリデーションオブジェクト

## 検証したバージョン
`Laravel Framework 9.34.0`
`Laravel Framework 8.83.25`

## はじめに

Laravelにはデフォルトでバリデーションルールがあり、これを用いることで容易に簡潔なコードでバリデーションができます。

https://readouble.com/laravel/9.x/ja/validation.html


しかし、「記号を1文字以上含むパスワード」のような複雑なルールに対応しようとすると、長く読みづらいコードになってしまいます。
複雑な要件のバリデーションに対応したい場合は、パスワードルールオブジェクトが便利です。

## パスワードルールオブジェクト

通常のバリデーションと同じように定義が可能です。

```php:FormRequest.php
use Illuminate\Validation\Rules\Password;

public function rules()
{
    return [
        'password' => ['required', 'max: 128', Password::min(8)],
]);
```
定義できるルールには以下があり、これらを組み合わせることも可能です。

```php:ルール一覧
// 最低８文字必要
Password::min(8)

// 最低１文字の文字が必要
Password::min(8)->letters()

// 最低大文字小文字が１文字ずつ必要
Password::min(8)->mixedCase()

// 最低一文字の数字が必要
Password::min(8)->numbers()

// 最低一文字の記号が必要
Password::min(8)->symbols()

// パスワードリストに登録されているか確認
Password::min(8)->uncompromised()
```

最後の`uncompromised()`は過去にデータ漏洩したことがあるかを確認してくれます。

例えば、脆弱なパスワードである "password" や "qwerty" などを入力すると引っかかります。ですので、パスワードの要件を縛りすぎることなく一部の危険なパスワードを回避することができます。さらに、半角全角英数字記号も満たしてるが脆弱な "Passw0rd!" などの設定も防ぐことが可能です。

また、引数に数値を入れることで、同一のデータリークにおいて、パスワードの出現回数がn回以下であることを確認することができます。

内部的には、have i been pwned のAPIを利用しているそうです。

https://haveibeenpwned.com/Passwords
## 日本語化
次に、日本語化の方法です。
検証したところ、8系と9系で**設定方法が異なります**。
※8系の方法は9系でも動作するためアップグレードする際には問題ありません。

### Laravel9
`validation.php`の`password`を以下のように差し替えます。
```php:validation.php
'password' => [
        'letters' => ':attributeは、少なくとも1つの文字が含まれていなければなりません。',
        'mixed' => ':attributeは、少なくとも大文字と小文字を1つずつ含める必要があります。',
        'numbers' => ':attributeは、少なくとも1つの数字が含まれていなければなりません。',
        'symbols' => ':attributeは、少なくとも1つの記号が含まれていなければなりません。',
        'uncompromised' => 'この:attributeは過去に漏洩したことのある脆弱な:attributeです。別の:attributeを入力してください。',
    ],
```
※翻訳内容は適時調整してください。

これで完了です。

### Laravel8

![スクリーンショット (277).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1081654/960c48e6-1fbb-565a-6d1f-52f24393632b.png)


実際に使うとこのような英語のメッセージが帰ってきます。 'パスワード' の文字だけ日本語になっている理由はこちらだけ`validation.php`によって設定されているからです。

バリデーション文の英語部分は
`vendor/laravel/framework/src/lluminate/Validation/Rules/Password.php`
に直に記述されており、`validation.php`では**設定できません**。

なお、9系では`Password.php`内で書き換えてから返す仕様に変わっており、設定が可能となっています。

<details><summary>9系、8系<code>Password.php</code>差分(気になる方向け)</summary><div>

```diff:Illuminate/Validation/Rules/Password.php
69c69
<      * If the password should has not been compromised in data leaks.
---
>      * If the password should not have been compromised in data leaks.
76c76
<      * The number of times a password can appear in data leaks before being consider compromised.
---
>      * The number of times a password can appear in data leaks before being considered compromised.
303,304d302
<             $value = (string) $value;
< 
306c304,307
<                 $validator->errors()->add($attribute, 'The :attribute must contain at least one uppercase and one lowercase letter.');
---
>                 $validator->errors()->add(
>                     $attribute,
>                     $this->getErrorMessage('validation.password.mixed')
>                 );
310c311,314
<                 $validator->errors()->add($attribute, 'The :attribute must contain at least one letter.');
---
>                 $validator->errors()->add(
>                     $attribute,
>                     $this->getErrorMessage('validation.password.letters')
>                 );
314c318,321
<                 $validator->errors()->add($attribute, 'The :attribute must contain at least one symbol.');
---
>                 $validator->errors()->add(
>                     $attribute,
>                     $this->getErrorMessage('validation.password.symbols')
>                 );
318c325,328
<                 $validator->errors()->add($attribute, 'The :attribute must contain at least one number.');
---
>                 $validator->errors()->add(
>                     $attribute,
>                     $this->getErrorMessage('validation.password.numbers')
>                 );
330,332c340
<             return $this->fail(
<                 'The given :attribute has appeared in a data leak. Please choose a different :attribute.'
<             );
---
>             return $this->fail($this->getErrorMessage('validation.password.uncompromised'));
345a354,376
>     }
> 
>     /**
>      * Get the translated password error message.
>      *
>      * @param  string  $key
>      * @return string
>      */
>     protected function getErrorMessage($key)
>     {
>         if (($message = $this->validator->getTranslator()->get($key)) !== $key) {
>             return $message;
>         }
> 
>         $messages = [
>             'validation.password.mixed' => 'The :attribute must contain at least one uppercase and one lowercase letter.',
>             'validation.password.letters' => 'The :attribute must contain at least one letter.',
>             'validation.password.symbols' => 'The :attribute must contain at least one symbol.',
>             'validation.password.numbers' => 'The :attribute must contain at least one number.',
>             'validation.password.uncompromised' => 'The given :attribute has appeared in a data leak. Please choose a different :attribute.',
>         ];
> 
>         return $messages[$key];
```

</div></details>

そこで、以下を`resources/lang/ja.json`に追加します。ja.jsonがない場合は作ってください。

```json:resources/lang/ja.json
{
    "The :attribute must contain at least one uppercase and one lowercase letter.":":attributeは、少なくとも大文字と小文字を1つずつ含める必要があります。",
    "The :attribute must contain at least one letter.":":attributeは、少なくとも1つの文字が含まれていなければなりません。",
    "The :attribute must contain at least one symbol.":":attributeは、少なくとも1つの記号が含まれていなければなりません。",
    "The :attribute must contain at least one number.":":attributeは、少なくとも1つの数字が含まれていなければなりません。",
    "The given :attribute has appeared in a data leak. Please choose a different :attribute.":"この:attributeは過去に漏洩したことのある脆弱な:attributeです。別の:attributeを入力してください。"
}
```
※翻訳内容は適時調整してください。![スクリーンショット (279).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1081654/e30c551c-f2b7-e90d-735b-c88055ad80aa.png)
![スクリーンショット (281)_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1081654/abf07ed6-d2bc-550b-c1b4-9a786ea47565.jpeg)

はい、無事に日本語化が完了しました！！
便利な機能なので使っていない方はぜひ使ってみてください！

# 参考サイト

* バリデーション方法
https://readouble.com/laravel/8.x/ja/validation.html
https://haveibeenpwned.com/Passwords


* 言語の設定方法
https://www.magutti.com/blog/laravel-8-password-rule

- 参考にさせていただいたコメント
https://qiita.com/shm/items/a70ea6e67e0bfdeeadb3#comment-ef916661671035903bb7