はじめに
最近「これはこうあるべきだ」というべき論を考えることが多く、バリデーターについても改めて考える機会があったのでまとめることにした。
べき論
バリデーターはシンプルに__「検証すべき要素がバリデーションに通るか通らないか(true or false)を返すべきだ」__
その際に__「もしfalseの場合には何がダメだったのかの失敗原因も返すべきだ」__
true/falseしか返してはいけないのにエラーメッセージも返さなければならないというのが「べき論」としてすでに矛盾している。
これをうまく回避する方法を以下のサンプルコードで示す。
サンプルコード
今回は例として「0より大きい数字ならバリデーションtrue, 0以下ならfalse」とした。
case1
<?php
function isValid($value){
if($value > 0){
return array(
'result' => true,
'message' => '',
);
} else {
return array(
'result' => false,
'message' => 'バリデート失敗',
);
}
}
$test1 = 0;
$test2 = 1;
var_dump(isValid($test1));
var_dump(isValid($test2));
結果
array(2) {
["result"]=>
bool(false)
["message"]=>
string(21) "バリデート失敗"
}
array(2) {
["result"]=>
bool(true)
["message"]=>
string(0) ""
}
愚直に値2つを配列として返している。
これは「べき論」で言うとtrue, false以外も返しているところが反している。
また扱いも複雑になり配列の要素(今回の例だとresult
, message
)にアクセスしなければならない。
case2
<?php
function isValid($value, &$message){
if($value > 0){
$message = '';
return true;
} else {
$message = 'バリデート失敗';
return false;
}
}
$test1 = 0;
$test2 = 1;
$message1 = '';
$message2 = '';
var_dump(isValid($test1, $message1));
var_dump($message1);
var_dump(isValid($test2, $message2));
var_dump($message2);
結果
bool(false)
string(21) "バリデート失敗"
bool(true)
string(0) ""
こちらは第二引数に参照渡しでメッセージ格納用の変数を予め渡しておき、その中にメッセージを入れておくという方法である。
「べき論」でいうと矛盾しておらずそこそこ有用そうに見える。
しかし検証すべき要素以外のものをバリデーターに渡さなければならず、かつ参照渡しなのでいつどこでメッセージの内容が書き換わるか分からずに副作用も強い。
例えばisValid
関数を呼び出したあとにmessage
を表示させるまでに間にmessage
変数を上書きすることが可能である点や、
上記のようにmessage1
, message2
のように変数を分けずに使いまわした場合に「どのタイミングでのメッセージなのか?」という状態が発生する。
有用だが十分に気を付けて使う必要がある。
case3
<?php
class Validator
{
private $message;
public function __construct(){
$this->message = '';
}
public function isValid($value){
if($value > 0){
$this->message = '';
return true;
} else {
$this->message = 'バリデート失敗';
return false;
}
}
public function getMessage(){
return $this->message;
}
}
$test1 = 0;
$test2 = 1;
$validator = new Validator();
$result1 = $validator->isValid($test1);
$message1 = $validator->getMessage();
var_dump($result1, $message1);
$result2 = $validator->isValid($test2);
$message2 = $validator->getMessage();
var_dump($result2, $message2);
結果
bool(false)
string(21) "バリデート失敗"
bool(true)
string(0) ""
多分これが1番良いと思います()
バリデーターを1つにクラスにしてしまえばプロパティにメッセージを持つことができるので、バリデーションと同時にメッセージを格納、欲しい時にgetする。
プロパティはprivate
にしておけば上書きされる心配はない。
バリデーターのインスタンスを別途立てることで状態を持つことも回避する。
おわりに
今回バリデーターの「あるべき論」について考え、ケースを3つ挙げました。
私の結論で言うとケース3が今のところ良いかなと思っていますが、これがベストかどうかは正直分かりません。
日々より良い方法を模索していきたいですね。
おまけ
仕事でphp
を書く機会が多いのでphp
で書きましたが、個人的にはruby
が好きなのでruby
で上記3パターンを書いたものも置いておきます。
何かコードを書く際に複数言語で書けるとかっこいいですね
case1
def isValid(value)
if value > 0
return {result: true, message: ''}
else
return {result: false, message: 'バリデート失敗'}
end
end
test1 = 0
test2 = 1
p isValid(test1)
p isValid(test2)
結果
{:result=>false, :message=>"バリデート失敗"}
{:result=>true, :message=>""}
php
の連想配列はruby
ではHash
で表現した。
case2
def isValid(arr)
if arr[0] > 0
arr[1] = ''
return true
else
arr[1] = 'バリデート失敗'
return false
end
end
test1 = 0
test2 = 1
message1 = ''
message2 = ''
arr1 = [test1, message1]
arr2 = [test2, message2]
p isValid(arr1)
p arr1[1]
p isValid(arr2)
p arr2[1]
結果
false
"バリデート失敗"
true
""
ruby
では参照渡しというものがないので上記ケース2をruby
で表現するのは難しかった。
配列を渡すことで擬似的に参照渡しを表現した。
case3
class Validator
def isValid(value)
if value > 0
@message = '';
return true
else
@message = 'バリデート失敗';
return false
end
end
def getMessage
@message
end
end
test1 = 0
test2 = 1
validator = Validator.new
p validator.isValid(test1)
p validator.getMessage
p validator.isValid(test2)
p validator.getMessage
結果
false
"バリデート失敗"
true
""
ruby
で書いてもやっぱりケース3が1番しっくりくる。
ruby
のプロパティはデフォルトでprivate
なのでより安全。