Laravelの標準バリデーションのわかりにくい挙動を実験して確かめたまとめ

# これはなに？

標準で多種多様なルールを用意してくれているLaravelのバリデータ。
中にはすごい高機能なものもありますが、そういうのは良いんですよ、ドキュメントをよく読むと思うので。

でも、一見カンタンでわかりやすいはずなのに、微妙に挙動が「思ってたんと違う」というものがあります。

そんな「当たり前だと思っていたルール」を、実際に動かしてみて、結果をまとめてみました。

すべてのバリデーションルールは対象にしていません……。

**公式ドキュメントはコチラ**
[Laravel5.6 バリデーション](https://readouble.com/laravel/5.6/ja/validation.html)

**参考記事**
[Laravelのバリデーションで指定できる内容をざっくりまとめ直しました。](https://qiita.com/fagai/items/9904409d3703ef6f79a2) 
いつもお世話になっています(^^)

[Laravel Validation メモ](https://qiita.com/a_yasui/items/780bb70a5c898b67637e) 
やりたいことが先に来ているので
具体的にどう使うかのイメージを掴むことができます。


# フィールドの有無（存在系）

最もよく使い、みなさんもよくご存知の required。しかしよく似た仲間が他にもいらっしゃいます。
どう違うの？なんでこんなにたくさんあるの？

#### required

> フィールドが入力データに存在しており、かつ空でないことをバリデートします。フィールドは以下の条件の場合、「空」であると判断されます。
・値がnullである。
・値が空文字列である。
・値が空の配列か、空のCountableオブジェクトである。
・値がパスのないアップロード済みファイルである。

#### filled

> フィールドが存在する場合、空でないことをバリデートします。

#### present

> フィールドが存在していることをバリデートしますが、存在していれば空を許します。


#### nullable

> フィールドがnullでも良いことをバリデートします。これは特にnull値を含む文字列や整数のようなプリミティブをバリデートする場合に便利です。


```php
$rule      = [ 'id' => 'required' ];
$validator = \Validator::make( $subject, $rule );
$validator->passes() ? true : false;
```

| | テスト対象 |  required | filled | present | nullable |
| --- | --- |:---:|:---:|:---:|:---:|
| 対象がない | `[]` | ❌ | 🔴 | ❌ | ○ | 
| NULL | `['id'=>null]` | ✕ | ✕ | ○ | ○ | 
| 空文字 | `['id'=>'']` | ✕ | ✕ | ○ | ○ | 
| 空配列 | `['id'=>[]]` | ✕ | ✕ | ○ | ○ | 
| 配列 | `['id'=>[1]]` | ○ | ○ | ○ | ○ | 
| 数値のゼロ | `['id'=>0]` | ○ | ○ | ○ | ○ | 
| 数値 | `['id'=>1]` | ○ | ○ | ○ | ○ | 
| 偽 | `['id'=>false]` | ○ | ○ | ○ | ○ | 
| 真 | `['id'=>true]` | ○ | ○ | ○ | ○ | 
| 文字列 | `['id'=>'string']` | ○ | ○ | ○ | ○ | 

あまり深く考えずに最もよく使う required ですが、対象がないと通りません。
これは例えば「データを新しく与えなければ既存のデータやデフォルト値を使うから要らない」といったケースでも入力が要求されます。
そういうケースに適したのが filled 。個人的な感覚で required というと filled の動きをするイメージです。では requierd の代わりに filled を使えば正解なのでしょうか？

さて、 nullable はなんと全通過で訳わかんないことになっています。
これらは、他の条件と組み合わせて使うことを前提としているので、試しに boolean と組わせてみましょう。

```php
$rule      = [ 'id' => 'boolean|required' ];
$validator = \Validator::make( $subject, $rule );
$validator->passes() ? true : false;
```

| | テスト対象 |  boolean | +required | +filled | +present | +nullable |
| --- | --- |:---:|:---:|:---:|:---:|:---:|
| 対象がない | `[]` | ○ | ❌ | ○ | ❌ | ○ | 
| NULL | `['id'=>null]` | ✕ | ✕ | ✕ | ✕ | 🔴 | 
| 空文字 | `['id'=>'']` | ○ | ❌ | ❌ | ○ | ○ | 
| 空配列 | `['id'=>[]]` | ✕ | ✕ | ✕ | ✕ | ✕ | 
| 配列 | `['id'=>[1]]` | ✕ | ✕ | ✕ | ✕ | ✕ | 
| 数値のゼロ | `['id'=>0]` | ○ | ○ | ○ | ○ | ○ | 
| 数値 | `['id'=>1]` | ○ | ○ | ○ | ○ | ○ | 
| 偽 | `['id'=>false]` | ○ | ○ | ○ | ○ | ○ | 
| 真 | `['id'=>true]` | ○ | ○ | ○ | ○ | ○ | 
| 文字列 | `['id'=>'string']` | ✕ | ✕ | ✕ | ✕ | ✕ | 

boolean などのパターン系は、**対象がない場合は通過** するのに **NULLセットされていると失敗** といった、矛盾をはらんだ美しくない挙動がデフォルトです。特にフォームからの入力だと、**空文字をNULLにセットし直すミドルウェア**があるので余計に予期せぬ動きをします。ここを整理するのが、彼ら「存在系」の役割です。

required は文字通り、きちんとデータセットされている場合だけ。対象がないと許可しません。
filled は空文字を「未入力」として弾きます。
present は逆に対象がないケースを弾きます。

この３つは、boolean と存在系の「AND」条件になっています。ところが。

nullable は、基本はBooleanですが、NULLセットのときに通過します。単純なANDやORじゃない。
でもこれが「デフォルト値があるから未入力でもOK」を許可するときに一番自然なルールかと思います。

なので、パターン系のルールを使う場合は、**required か nullable と組み合わせて使うのが基本**と考えておいたほうが良さそうです。

# 数値系

integerだったら、001 は通ると思いません？
そんな数値系のテスト結果です。

#### boolean

> フィールドが論理値として有効であることをバリデートします。受け入れられる入力は、true、false、1、0、"1"、"0"です。

#### integer 

> フィールドが整数値であることをバリデートします。

#### numeric 

> フィールドは数値であることをバリデートします。

```php
$rule      = [ 'id' => 'boolean' ];
$validator = \Validator::make( $subject, $rule );
$validator->passes() ? true : false;
```

| | テスト対象 |  boolean | integer | numeric | regex:/^\s*-?[0-9,]+\s*$/ |
| --- | --- |:---:|:---:|:---:|:---:|
| 対象がない | `[]` | ○ | ○ | ○ | ○ | 
| NULL | `['id'=>null]` | ✕ | ✕ | ✕ | ✕ | 
| 空文字 | `['id'=>'']` | ○ | ○ | ○ | ○ | 
| 空配列 | `['id'=>[]]` | ✕ | ✕ | ✕ | ✕ | 
| 配列 | `['id'=>[1]]` | ✕ | ✕ | ✕ | ✕ | 
| 数値のゼロ | `['id'=>0]` | ○ | ○ | ○ | ○ | 
| 数値 | `['id'=>1]` | ○ | ○ | ○ | ○ | 
| 整数 | `['id'=>65536]` | ✕ | ○ | ○ | ○ | 
| 小数 | `['id'=>1.05]` | ✕ | ✕ | ○ | ❌ | 
| 偽 | `['id'=>false]` | ○ | ✕ | ✕ | ✕ | 
| 真 | `['id'=>true]` | ○ | 🔴 | ✕ | ✕ | 
| 文字列 | `['id'=>'string']` | ✕ | ✕ | ✕ | ✕ | 
| ゼロの文字列 | `['id'=>'0']` | 🔴 | ○ | ○ | ○ | 
| 数値の文字列 | `['id'=>'1']` | 🔴 | ○ | ○ | ○ | 
| 負の値の文字列 | `['id'=>'-1']` | ✕ | ○ | ○ | ○ | 
| 小数の文字列 | `['id'=>'1.05']` | ✕ | ✕ | ○ | ❌ | 
| ゼロ付 | `['id'=>'01']` | ✕ | ❌ | ○ | 🔴 | 
| 空白付 | `['id'=>' 1']` | ✕ | ○ | ○ | 🔴 | 
| カンマ付 | `['id'=>'1,000']` | ✕ | ✕ | ✕ | 🔴 | 


boolean はドキュメントにある通り、文字列でも通ります。booleanという字面の印象よりは柔軟ですね。

integer では意外と、ゼロ付きの `'01'` が通りません。
以前IDをゼロ埋めしているケースで通らなくてハマりました。
trueが通るのも意外です。

numeric だともう少しゆるくなりますが、小数が通ります。
逆に、カンマ付きは通しても良いんじゃないか。
真の integer はどこにおるのだ！と捜索しましたが、結局正規表現パターンしかなさそう。


# 型

#### string

> フィルードは文字列タイプであることをバリデートします。フィールドがnullであることも許す場合は、そのフィールドにnullableルールも指定してください。

#### date 

> パリデーションされる値はPHP関数のstrtotimeを使用し確認されます。

#### array

> フィールドが配列タイプであることをバリデートします。


```php
$rule      = [ 'id' => 'string' ];
$validator = \Validator::make( $subject, $rule );
$validator->passes() ? true : false;
```

| | テスト対象 |  string | date | array |
| --- | --- |:---:|:---:|:---:|
| 対象がない | `[]` | ○ | ○ | ○ | 
| NULL | `['id'=>null]` | ❌ | ✕ | ✕ | 
| 空文字 | `['id'=>'']` | 🔴 | ○ | ○ | 
| 空配列 | `['id'=>[]]` | ✕ | ✕ | ○ | 
| 配列 | `['id'=>[1]]` | ✕ | ✕ | ○ | 
| 数値のゼロ | `['id'=>0]` | ✕ | ✕ | ✕ | 
| 数値 | `['id'=>1]` | ✕ | ✕ | ✕ | 
| 整数 | `['id'=>65536]` | ✕ | ✕ | ✕ | 
| 小数 | `['id'=>1.05]` | ✕ | ✕ | ✕ | 
| 偽 | `['id'=>false]` | ✕ | ✕ | ✕ | 
| 真 | `['id'=>true]` | ✕ | ✕ | ✕ | 
| 文字列 | `['id'=>'string']` | ○ | ✕ | ✕ | 
| 日付 | `['id'=>'2018-01-01 00:00:00']` | ○ | ○ | ✕ | 
| 日付 | `['id'=>'2018/01/01']` | ○ | ○ | ✕ | 
| 日付 | `['id'=>'2018.01.01']` | ○ | ❌ | ✕ | 
| 日付 | `['id'=>'180101']` | ○ | ❌ | ✕ | 
| 日付 | `['id'=>'01:24']` | ○ | ❌ | ✕ | 
| 日付 | `['id'=>'today']` | ○ | ❌ | ✕ | 
| 異常な日付 | `['id'=>'2108-13-14 00:00:00']` | ○ | ✕ | ✕ | 

stringのドキュメントに「nullable」を使えとあるのは、上の方にも書きましたが、'' を null に置き換えるミドルウェアがあるからでしょうね。以前のバージョンの入門書だと「そのミドルウェアを外せ」とありましたが、Laravelさんが対策をしたカタチです。

date は `strtotime` を使うと書いてありますが、`'today'` が通りません。実は内部でもう1つ、`date_parse` も使っています。こいつが結構厳しいです。


# 特殊なパターン

#### accepted 

> そのフィールドがyes、on、1、trueであることをバリデートします。これは「サービス利用規約」同意のバリデーションに便利です。

```php
$rule      = [ 'id' => 'accepted' ];
$validator = \Validator::make( $subject, $rule );
$validator->passes() ? true : false;
```

| | テスト対象 |  accepted |
| --- | --- |:---:|
| 対象がない | `[]` | ✕ | 
| NULL | `['id'=>null]` | ✕ | 
| 空文字 | `['id'=>'']` | ✕ | 
| 空配列 | `['id'=>[]]` | ✕ | 
| 配列 | `['id'=>[1]]` | ✕ | 
| 数値のゼロ | `['id'=>0]` | ✕ | 
| 数値 | `['id'=>1]` | ○ | 
| 整数 | `['id'=>65536]` | ✕ | 
| 小数 | `['id'=>1.05]` | ✕ | 
| 偽 | `['id'=>false]` | ✕ | 
| 真 | `['id'=>true]` | ○ | 
| 文字列 | `['id'=>'string']` | ✕ | 
| ゼロの文字列 | `['id'=>'0']` | ✕ | 
| 数値の文字列 | `['id'=>'1']` | ○ | 
| 負の値の文字列 | `['id'=>'-1']` | ✕ | 
| 小数の文字列 | `['id'=>'1.05']` | ✕ | 
| ゼロ付 | `['id'=>'01']` | ✕ | 
| 空白付 | `['id'=>' 1']` | ✕ | 
| カンマ付 | `['id'=>'1,000']` | ✕ | 
| 小文字yes | `['id'=>'yes']` | 🔴 | 
| 大文字YES | `['id'=>'YES']` | ❌ | 
| 小文字on | `['id'=>'on']` | 🔴 | 
| 大文字ON | `['id'=>'ON']` | ❌ | 
| 小文字true | `['id'=>'true']` | 🔴 | 
| 大文字TRUE | `['id'=>'TRUE']` | ❌ | 
| no | `['id'=>'no']` | ✕ | 
| off | `['id'=>'off']` | ✕ | 

ドキュメントの通りですが、意外と大文字が通りませんでした……。



# 配列

```php
$rule      = [ 'id.*.type' => 'integer' ];
$validator = \Validator::make( $subject, $rule );
$validator->passes() ? true : false;
```

| | テスト対象 | `'id'=>...` | `'id.*'=>...` | `'id.*.type'=>...` |
| --- | --- |:---:|:---:|:---:|
| 数値 | `['id'=>5]` | ○ | ○ | ○ | 
| 小数 | `['id'=>1.05]` | ✕ | ○ | ○ | 
| 空配列 | `['id'=>[]]` | ✕ | ○ | ○ | 
| 配列 | `['id'=>[1,2,3]]` | ✕ | 🔴 | ○ | 
| 配列(重複) | `['id'=>[1,2,1]]` | ✕ | 🔴 | ○ | 
| 小数のある配列 | `['id'=>[1,2.5,3]]` | ✕ | ❌ | ○ | 
| 2次元配列 | ['id'=>[<br>&emsp;['type'=>1],<br>&emsp;['type'=>2],<br>&emsp;['type'=>3]<br> ]]` | ✕ | ✕ | 🔴 | 
| 2次元配列(重複) | ['id'=>[<br>&emsp;['type'=>1],<br>&emsp;['type'=>2],<br>&emsp;['type'=>1]<br>]] | ✕ | ✕ | 🔴 | 
| 小数のある2次元配列 | ['id'=>[<br>&emsp;['type'=>1],<br>&emsp;['type'=>2],<br>&emsp;['type'=>1.5]<br>]] | ✕ | ✕ | ❌ | 

distinct を理解する前に、**配列を検査する方法** を理解する必要があります。

通常のルールは `$rules=['id'=>'integer']` と書きます。
これは、`$subject=['id'=>3.14]` となっているときの、`$subject['id'] = 3.14` が検査対象値です。

配列の検査は、配列の中身を1つずつ個別に検査します。
例えば、`$rules=['id.*'=>'integer']` と書いた場合、
これは、`$subject=['id'=>[3.14, 5, 6]]` となっているときの、
`$subject['id'][0] = 3.14`、
`$subject['id'][1] = 5`、
`$subject['id'][2] = 6` 
の3値が検査対象で、すべてクリアしたときのみ通ります。

多次元配列やハッシュにも使えて、
例えば、`$rules=['id.*.type'=>'integer']` と書いた場合、
これは、`$subject=['id'=>[['type'=>3.14], ['type'=>5], ['type'=>6]]]` となっているときの、
`$subject['id'][0]['type'] = 3.14`、
`$subject['id'][1]['type'] = 5`、
`$subject['id'][2]['type'] = 6` 
の3値が検査対象になります。

上記表の色付きのところが、Integerの判定が効いているところです。注意すべきは、**指定の配列要素がない場合は通る** ところです。ここは、requiredを組み合わせることと、`'id'`本体にも `array|required` と制約することで拒否可能です。

```php
$rule      = [ 'id'=>'array|required', 'id.*.type' => 'integer|required' ];
$validator = \Validator::make( $subject, $rule );
$validator->passes() ? true : false;
```

| | テスト対象 | `'id'=>...` | `'id.*'=>...` | `'id.*.type'=>...` |
| --- | --- |:---:|:---:|:---:|
| 数値 | `['id'=>5]` | ○ | ❌ | ❌ | 
| 小数 | `['id'=>1.05]` | ✕ | ❌ | ❌ | 
| 空配列 | `['id'=>[]]` | ✕ | ❌ | ❌ | 
| 配列 | `['id'=>[1,2,3]]` | ✕ | ○ | ❌ | 
| 配列(重複) | `['id'=>[1,2,1]]` | ✕ | ○ | ❌ | 
| 小数のある配列 | `['id'=>[1,2.5,3]]` | ✕ | ✕ | ❌ | 
| 2次元配列 | ['id'=>[<br>&emsp;['type'=>1],<br>&emsp;['type'=>2],<br>&emsp;['type'=>3]<br> ]]` | ✕ | ✕ | ○ | 
| 2次元配列(重複) | ['id'=>[<br>&emsp;['type'=>1],<br>&emsp;['type'=>2],<br>&emsp;['type'=>1]<br>]] | ✕ | ✕ | ○ | 
| 小数のある2次元配列 | ['id'=>[<br>&emsp;['type'=>1],<br>&emsp;['type'=>2],<br>&emsp;['type'=>1.5]<br>]] | ✕ | ✕ | ✕ | 



#### distinct 

ということを踏まえて、その検査対象の値に重複がないかを検査するルールです。
とてもよく設計されていると思います。

`'id'=>'distinct'`としても検査対象値が1つしか無いので、常に通ります。
誤った使い方、ということになります。

```php
$rule      = [ 'id.*.type' => 'distinct' ];
$validator = \Validator::make( $subject, $rule );
$validator->passes() ? true : false;
```

| | テスト対象 | `'id'=>...` | `'id.*'=>...` | `'id.*.type'=>...` |
| --- | --- |:---:|:---:|:---:|
| 数値 | `['id'=>5]` | ○ | ○ | ○ | 
| 小数 | `['id'=>1.05]` | ○ | ○ | ○ | 
| 空配列 | `['id'=>[]]` | ○ | ○ | ○ | 
| 配列 | `['id'=>[1,2,3]]` | ○ | ○ | ○ | 
| 配列(重複) | `['id'=>[1,2,1]]` | ○ | ❌ | ○ | 
| 小数のある配列 | `['id'=>[1,2.5,3]]` | ○ | ○ | ○ | 
| 2次元配列 | ['id'=>[<br>&emsp;['type'=>1],<br>&emsp;['type'=>2],<br>&emsp;['type'=>3]<br>]]] | ○ | ○ | ○ | 
| 2次元配列(重複) | ['id'=>[<br>&emsp;['type'=>1],<br>&emsp;['type'=>2],<br>&emsp;['type'=>1]<br>]]] | ○ | ○ | ❌ | 
| 小数のある2次元配列 | ['id'=>[<br>&emsp;['type'=>1],<br>&emsp;['type'=>2],<br>&emsp;['type'=>1.5]<br>]]] | ○ | ○ | ○ | 


# その他

他にもいろいろありますが、~~公式ドキュメントを見てください。~~ またお時間あったらやってみようと思います。ややこしそうなのは、required_if 系がなぜかやたら多いのとか、confirm や same など「他フィールドとの関連」系でしょうか。あと、Eloquent系の実行速度も気になります……。

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

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

- 【バリデータ】 [フロー図で理解するLaravelバリデータの仕組みと、チーム開発でのケーススタディ](https://qiita.com/kd9951/items/e797b17c03fc8e8f414b)
- 【バリデータ】 [第2回 Laravelのrequire_if系バリデーションのわかりにくい挙動を実験して確かめたまとめ](https://qiita.com/kd9951/items/0617ad4c9236a64c5367)
- 【バリデータ】 [Modelでバリデーションする(2行で) ＋ Laravel5のバリデーションの仕組み解説(5秒で)](https://qiita.com/kd9951/items/c06d107aa4018295aa25)
- 【サービスコンテナ】 [もう怖くない こんなにカンタンだから今日から Laravelのサービスコンテナ を使ってみて！](https://qiita.com/kd9951/items/951ab700f28d1d49c9c9)
- 【サービスコンテナ】 [メソッドインジェクションがしたい！](https://qiita.com/kd9951/items/2328e2f8add9037bd990)