Edited at

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


これはなに?

標準で多種多様なルールを用意してくれているLaravelのバリデータ。

中にはすごい高機能なものもありますが、そういうのは良いんですよ、ドキュメントをよく読むと思うので。

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

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

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

公式ドキュメントはコチラ

Laravel5.6 バリデーション

参考記事

Laravelのバリデーションで指定できる内容をざっくりまとめ直しました。

いつもお世話になっています(^^)

Laravel Validation メモ

やりたいことが先に来ているので

具体的にどう使うかのイメージを掴むことができます。


フィールドの有無(存在系)

最もよく使い、みなさんもよくご存知の required。しかしよく似た仲間が他にもいらっしゃいます。

どう違うの?なんでこんなにたくさんあるの?


required


フィールドが入力データに存在しており、かつ空でないことをバリデートします。フィールドは以下の条件の場合、「空」であると判断されます。

・値がnullである。

・値が空文字列である。

・値が空の配列か、空のCountableオブジェクトである。

・値がパスのないアップロード済みファイルである。



filled


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



present


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



nullable


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


$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 と組わせてみましょう。

$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 は逆に対象がないケースを弾きます。

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

nullable は、基本はBooleanですが、NULLセットのときに通過します。単純なANDやORじゃない。

でもこれが「デフォルト値があるから未入力でもOK」を許可するときに一番自然なルールかと思います。

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


数値系

integerだったら、001 は通ると思いません?

そんな数値系のテスト結果です。


boolean


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



integer


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



numeric


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


$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


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


$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であることをバリデートします。これは「サービス利用規約」同意のバリデーションに便利です。


$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']

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


配列

$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'=>[
 ['type'=>1],
 ['type'=>2],
 ['type'=>3]
]]`


🔴

2次元配列(重複)
['id'=>[
 ['type'=>1],
 ['type'=>2],
 ['type'=>1]
]]


🔴

小数のある2次元配列
['id'=>[
 ['type'=>1],
 ['type'=>2],
 ['type'=>1.5]
]]


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 と制約することで拒否可能です。

$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'=>[
 ['type'=>1],
 ['type'=>2],
 ['type'=>3]
]]`


2次元配列(重複)
['id'=>[
 ['type'=>1],
 ['type'=>2],
 ['type'=>1]
]]


小数のある2次元配列
['id'=>[
 ['type'=>1],
 ['type'=>2],
 ['type'=>1.5]
]]



distinct

ということを踏まえて、その検査対象の値に重複がないかを検査するルールです。

とてもよく設計されていると思います。

'id'=>'distinct'としても検査対象値が1つしか無いので、常に通ります。

誤った使い方、ということになります。

$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'=>[
 ['type'=>1],
 ['type'=>2],
 ['type'=>3]
]]]


2次元配列(重複)
['id'=>[
 ['type'=>1],
 ['type'=>2],
 ['type'=>1]
]]]


小数のある2次元配列
['id'=>[
 ['type'=>1],
 ['type'=>2],
 ['type'=>1.5]
]]]



その他

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


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

Laravelのちょっとマニアックな視点から、誰も書かない記事を書いています(笑

合わせてご覧いただけると幸いです(^^)