使っちゃダメ！Laravelの地雷バリデーションと標準ルールを禁止（オーバーライド）する方法

# TL;DR

- Laravelのバリデーションルールには、期待される結果と実際が異なるので使わないほうがいいものがある
- 標準装備のルールの挙動を変えるには `Validator::Extends` メソッドは無力
- 標準の Validator をまるごと置き換える
- こうすると標準ルールを**変更**できたりカスタムルールも追加できたりするが、<br>混乱を招くので変更はNO GOOD、追加は Extends でやるべし

# 💣地雷ルール

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129854/659b9270-be16-5b26-1899-5b3a3828ee66.png)

すみません。タイトルと見出しは釣りです（笑）

知らずに使うとアレなので知っておいたほうがいいですよー、
忘れるといけないのでメモっておきますねー、くらいが真意。

まぁ中には「これってバグじゃないの？」というものもあるので、
将来的にはしれっと直っている可能性はあります。

## email

| ルール | 判定結果 | 入力 |
|:---:|:---:|:--- |
| email | 🔴 | test@test.com |
| email | 🔴 | nor.mal@test.net |
| email | 🔴 | goo+gle@gmail.com |
| email | ❌ | docomo.@test.co.jp (実在するドコモアドレス) |
| email | ❌ | do..como@test.test (実在するドコモアドレス) |
| email | ❌ | test@test |
| email | ❌ | test@.test |
| email | ❌ | 漢字@test.test |

~~Laravelに限った話ではないので、わりと有名な話ですが、
docomoさんが独自に使用可能にしたメールアドレスを弾きます。
日本のモバイルユーザーさんに使ってもらいたいサービスを作るなら、
emailルールには配慮が必要です。~~

~~ただ、2019年現在、すでに「昔話」になりつつあるようですし、
丁寧に受け付けたとしても、いざ自分が送信するとき
送信メールサーバーやサービスに蹴られてしまうこともあるようなので
最終的にはそのメールサーバーの対応力次第ということですね。~~

2021年現在、docomoメールアドレスは
docomo自体が「なんだっけそれ？」と言ったとか言わないとか
そんなウワサもあったくらい過去の話なので
もはや考慮に値せず。
emailルールは公式ままで良さそうです。

ただ、HTML5のブラウザ標準のemail入力ボックス
`<input type="email">` より厳しいので、
「フォームは通ったけどサーバーサイドで弾かれる」といったこともあるのは留意しましょう。


### 代替案

毎回 **regex** ルールを書くのは賢くないので、
**docomo_email** といった独自ルールを追加しましょう。


## alpha_num

| ルール | 判定結果 | 入力 |
|:---:|:---:|:--- |
| alpha_num | 🔴 | abcABC0123456 |
| alpha_num | ❌ | _ (アンダーバー) |
| alpha_num | ❌ | . (ドット) |
| alpha_num | ❌ | - (ハイフン) |
| alpha_num | 🔴 | abcABC0123456あいう |
| alpha_num | 🔴 | abcABC0123456ＡＢＣ |
| alpha_num | 🔴 | abcABC0123456１２３ |

Laravelのalpha_numは日本語が通ります。なんでやねん！

商品コード（ A000123 的なものを想定 ）に
自由に日本語が使えるという大事件に発展するところでした……。

### 代替案

毎回 **regex** ルールを書くのは賢くないので、
**my_alpha_num** といった独自ルールを追加しましょう。
ただ、もし「商品コードの書式」というのが別途決まっているなら、
**product_code** という独自ルールを追加したほうがいいかもしれません。


## date

| ルール | 判定結果 | 入力 |
|:---:|:---:|:--- |
| date | 🔴 | '2019-01-12' |  
| date | 🔴 | '2019-01-12 12:10' |  
| date | 🔴 | '2019-01-12 23:59:59' |  
| date | 🔴 | '2019-01' |  
| date | 🔴 | new \DateTime('2019-01-12') |  
| date | 🔴 | new \Carbon\Carbon('2019-01-12') |  
| date | ❌ | '23:59:59' |  
| date | ❌ | 'tomorrow' |  

地雷というほどでもないのですが、
イメージと実際の動きが「思ってたんと違う」という人がいるので注意が必要です。

**「日」がなくても通る** し、**「時刻」があっても通る** のが悩みのタネ。
日時やタイムゾーンまで何でも入る **DateTime でもOK**。

つまりdateルールでは、月入力枠なのか、日付入力枠なのか、日時入力枠なのかを区別できません。
日付入力欄のつもりだったのに、ユーザーから「2019-12」って入力があったら、
それって何日のこと？ そもそも月入力だと勘違いしてる？ってね。

とりあえずdateにしておけ！としちゃうと後から悩むことになるので、
最初から使えないようにしておくのも手です。

＃おそらく通過後に受け取ったら速やかに Y-m-d に変換する想定なんでしょうけど。
だったら datetimeルールや timeルールも用意しておいてほしいなぁ。


### 代替案

**date_format** で明確に書式を書くようにするか、
**month_string、date_string、datetime_string** と厳格な書式（とクラスじゃなくて文字列型であること）を明確にしたルールを追加してはいかがでしょうか。

| ルール | 判定結果 | 入力 |
|:---:|:---:|:--- |
| date_format:Y-m-d H:i:s | ❌ | '2019-01-12' |  
| date_format:Y-m-d H:i:s | ❌ | '2019-01-12 12:10' |  
| date_format:Y-m-d H:i:s | 🔴 | '2019-01-12 23:59:59' |  
| date_format:Y-m-d H:i:s | ❌ | '2019-01' |  
| date_format:Y-m-d H:i:s | ❌ | new \DateTime('2019-01-12') |  
| date_format:Y-m-d H:i:s | ❌ | new \Carbon\Carbon('2019-01-12') |  
| date_format:Y-m-d H:i:s | ❌ | '23:59:59' |  
| date_format:Y-m-d H:i:s | ❌ | 'tomorrow' |  
    


# 地雷ルールを禁止する

Laravel公式ドキュメントでは、ルールの追加は `Validator::extends`を使えとあります。
が、標準装備のルールに対しては Extends メソッドは無力です。

＃これは Exntends で追加されたルールは**マジックメソッド的**に追加されるので、Validatorの**実メソッド**として実装されている標準ルール `validateAlphaNum` などに勝てないからです。（無駄に強すぎるので、将来変更されるかも……）

というわけで、標準ルールを置き換えるには、標準のバリデータクラスの実メソッドをオーバーライド。派生クラスを用意して、置き換えます。

## バリデータの拡張

次のファイルを追加します。
場所はどこでもいいのですが、Extensionsというディレクトリを作ってそこに入れています。
とりあえず alpha_num をオーバーライド。他にも必要でしたら追加していきます。

```php:app/Extentions/Validator.php
namespace App\Extensions;

use Illuminate\Validation\Validator as BaseValidator;

class Validator extends BaseValidator
{
    // バリデーションルールは「validate+ルール名のキャメルケース」というメソッド名
    // 引数はほぼこの2つですが時々「数が違う」と怒られるので、その場合は後の2つを足しましょう
    public function validateAlphaNum($attribute, $value /*, $parameters, $validator*/)
    {
        // 単にダメ！というだけでなく、代替手段に誘導してあげましょう
        throw new \LogicException(
            '標準のalpha_numは日本語を通すので使用禁止。'
            .'my_alpha_numを使用してください。'
        );
    }
}
```


## 拡張したバリデータを使う

AppServiceProviderにて、先ほど作成したクラスを呼びます。

```php:app\Providers\AppServiceProvider.php
use App\Extensions\Validator;

// ......

    public function boot()
    {
        // バリデータを独自拡張したものに差し替え
        \Validator::resolver(function ($translator, $data, $rules, $messages) {
            return new Validator($translator, $data, $rules, $messages);
        });

        // ......
```

# 地雷ルールを置き換えたい

上記手段は標準ルールをオーバーライドしているので、
ここに違うロジックを書いて標準ロジックを「置き換える」こともできます。

ただ、標準ルールと同じ名前なのに挙動が異なるのは、特に複数メンバのプロジェクトでは混乱の元。
それにValidator拡張クラスに新しいルールを追加できることに気づくと、そこはカオスの入り口です。

Validatorは標準の禁止に限っておき、
変更や追加は標準のExtendsメソッドを使うとしておいたほうが良さそうです。

# 感想

バリデーションの本当の目的は**「入力した人の意図を明確にする（曖昧な指示を許容しない）」**ことだと思っているので、dateバリデータは困ったちゃんだなぁと思ったのがこの記事のきっかけです。

もっと厳しくすると、例えば入力される値は絶対にYMD形式の文字列だ！とすると、そのメソッドを実装するプログラマが、値チェックや値変換を書かなくていいので楽できます。頼もしすぎる。

でも逆に、入力する側のプログラマが大変になります。「ちょっと！こっちはY-M-DなのになんでYMDなんだよ！」って。それがエンドユーザーだと「日付って言われたから2019-12-31って入れたのにダメってなんだよ！ 20191231で入れろって？ そのくらいわかるだろ！」とクレームに繋がります。厳しすぎるのは不親切。

プログラムの基本原則に**「入力は甘く、出力は厳しく」**というのがあるそうですが、なんでもかんでも受け入れる甘々ではプログラマさんが大変なので、その本当の基準は**「入力する人の意図が曖昧にならない範囲」**で考えると良いんじゃないかなーと思っています。

さて。

「他にもこんな地雷があるよー」とかお気づきのことがあれば
お気軽にコメントいただけると幸いです（＾＾）


# こんな記事も書いています

Laravelのちょっとマニアックな視点から、誰も書かない記事を書いています（笑
合わせてご覧いただけると幸いです（＾＾）


- 【バリデータ】 [① Laravelの標準バリデーションのわかりにくい挙動を実験して確かめたまとめ](https://qiita.com/kd9951/items/abd063828e33a61c8c58)
- 【バリデータ】 [② Laravelのrequired_if系バリデーションのわかりにくい挙動を実験して確かめたまとめ](https://qiita.com/kd9951/items/0617ad4c9236a64c5367)
- 【バリデータ】 [フロー図で理解するLaravelバリデータの仕組みと、チーム開発でのケーススタディ](https://qiita.com/kd9951/items/e797b17c03fc8e8f414b)
- 【バリデータ】 [Modelでバリデーションする(2行で) ＋ Laravel5のバリデーションの仕組み解説(5秒で)](https://qiita.com/kd9951/items/c06d107aa4018295aa25)