文字数のバリデーションで気をつけること【Laravel】

## はじめに
Laravelで文字数のバリデーションを実装するときの注意点のお話です。


## 環境
Laravel8
PHP 8.0.12

## バリデーションの方法おさらい

Laravelは一般的なバリデーションを簡単に実装するための方法を３つ用意してくれています。

- Illuminate\Http\Requestオブジェクトのvalidateメソッドを使用する
- フォームリクエストを作成する
- Validatorファサードを使う

　[バリデーション 8.x Laravel](https://readouble.com/laravel/8.x/ja/validation.html)

これら以外にもルールオブジェクトを利用することでカスタムバリデーションを実装することができます。
ドキュメントには
>より複雑なバリデーションシナリオの場合は、「フォームリクエスト」を作成することをお勧めします。

とありますが、個人的にControllerはスッキリさせたいので、基本的にはフォームリクエストを作成したいところです。

今回はバリデーションの実装方法に関わらず起こりうる問題についてお話するので、実装方法についてはこれぐらいにしておきます。

## 文字数のバリデーションの注意点

一般的な方法では、以下のような配列を引数や戻り値にすることでバリデーションを実装していると思います。

```php
[
    'title' => 'required|string|max:255',
    'content' => 'required|string',
];

```
もしくは

```php
[
    'title' => ['required', 'string', 'max:255'],
    'content' => ['required', 'string']
];
```

文字数の制限を実装する場合、
`max:255`や`min:6`のような記述で文字数をバリデーションできます。

しかし、この実装方法ではあなたの思っているようなバリデーションにはなっていないかもしれません。

なぜなら、この`max:255`や`min:6`といったようなバリデーションは、 それぞれの文字を<font color="OrangeRed">半角や全角に関わらず全て１文字とカウントしている</font>からです。

つまりPHPの埋め込み関数であるmb_strlenと同じような挙動ですね。

しかし、困ったことに我々の世界線では、1バイトで表される半角英数字のことを一文字とさし、2バイトで表される全角の文字は2文字、それ以上のバイト数で表される文字数は3文字、４文字と数える場合があります。

そのため、このような数え方にあわせたシステムの場合、max:255のような書き方では対応できません。

とはいえ、ここまで書いておいてなんですが、バリデーションはデータベース保存前に行う処理であることが多く、MySQLなどのデータベースの文字数制限は基本的にバイト数ではなく文字数に依存しているようなので、Laravelのmaxやminバリデーションの仕様は特に意識していなくても問題ないのかもしれません（つまりデータベースで文字数上限255とかにしてるなら、Laravelでもmax:255にしておけば十分)。




## もしバイト数で文字を数えたいなら
カスタムバリデーションを利用します。

>Laravelは有用な数多くのバリデーションルールを提供しています。ただし、独自のものを指定することもできます。カスタムバリデーションルールを登録する１つの方法は、ルールオブジェクトを使用することです。

[バリデーション 8.x Laravel - カスタムバリデーションルール](https://readouble.com/laravel/8.x/ja/validation.html#:~:text=required%27%2C%20Password%3A%3Adefaults()%5D%2C-,%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%83%90%E3%83%AA%E3%83%87%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%AB%E3%83%BC%E3%83%AB,-%E3%83%AB%E3%83%BC%E3%83%AB%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE)

さっそくカスタムルームを作っていきます。

```
$ php artisan make:rule MaxWordCountValidation
```

Ruleインターフェイスを継承したMaxWordCountValidationクラスが生成されます。
生成されたファイルを以下のように書き換えます。

```php:MaxWordCountValidation
<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class MaxWordCountValidation implements Rule
{
    /**
     * Create a new rule instance.
     *
     * @return void
     */
    public function __construct(private int $MaxWordCount)
    {
    }

    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value): bool
    {
        $trim = str_replace(array("\r\n", "\r", "\n"), '', $value);
        $Length = strlen($trim);
        return $this->MaxWordCount >= $Length;
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return ":attributeは{$this->MaxWordCount}文字以下で入力してください";
    }
}
```


passesメソッド内にバリデーションのロジックを記述します。バリデーションに引っかかるか引っかからないかなので、戻り値はtrueかfalseになります。

また、messageメソッドで文字列を返すことで、バリデーションエラーメッセージもカスタムできます。

今回は任意の整数をコンストラクタの引数に取り、ユーザーの入力した値の文字数がその整数以下ならセーフというルールオブジェクトを作成しました。

これをバリデーションで適用させるには以下のようにします。

```php
[
    'title' => 'required|string|max:255',
    'content' => ['required','string', new MaxWordCountValidation(255)],
];

```

カスタムバリデーションを利用する際は、文字列ではなく配列で各バリデーションを指定する必要があります。

これで、バイト数による文字数制限のバリデーションを実装することができました。

## さいごに
今回は文字数のバリデーションを実装する際に気をつける点と、カスタムバリデーションの実装のお話でした。

Laravelの標準バリデーションに限らず埋め込み関数やフレームワークの標準機能を使うときは、どういった仕様で自分のやりたいことに適しているかしっかり理解してから使用するようにしましょう。と、自戒の念を込めて...。