はじめに
先月からPHP/FuelPHPを使うことになって色々勉強しながら実装中。
その中で、特にFuelPHP周りの機能や挙動について調べたことをメモとして残していきます。
※PHP7.1.9/FuelPHP1.8を使用
Validationクラス
バリデーションの仕組みを提供するクラス。名前まんま。
Fieldsetから入ったので最初は戸惑ったが、ValidationはValidationでフィールド定義を内部で保持していて(validation->fields)、それぞれについてどのバリデーションを実行するか(field->rules)を管理している模様。
runメソッド
バリデーションを実行する。引数を指定すればその値に対してバリデーションを実行。なければInput::param()を使うみたい。
前項で書いたとおりFieldsetから入ったので「なんでFieldset::forgeするときにパラメータ渡してるのに、runすると渡したパラメータが無視されてるんだ???」て悩んでたけど、そういう仕様だと分かれば問題はない。
複数の「バリデーション済みのフィールド」を参照したい
カスタムバリデーションを実装するとき、複数のフィールドの値を参照したバリデーションを作りたい…というのはよくあること。
バリデーションメソッドの中から他のフィールドの値を参照する方法を調べていたところ、Validation::active()->input()
を使えばいいらしい…のだけど、ちょっと待って。
個別の入力フィールドにはそれぞれrequiredやmax_length等々をadd_ruleしてるわけで、カスタムバリデーションはそれらが全てパスしてる前提で書いている。
さらに調べてみたところ、Validation::active()->validated()
を使えば、バリデーション済みの値だけが取れる(エラーが検出されたフィールドは入ってこない)ようだけど…そもそもカスタムバリデーション実行時点で、個別のバリデーションが完了している保障はあるのか?
バリデーションはaddした順番に実行される
そんなわけでFWのソースを追いかけてみたところ、以下のような処理になっていた。
$this->validated = array();
$this->errors = array();
$this->input = $input ?: array();
$fields = $this->field(null, true);
foreach($fields as $field)
{
static::set_active_field($field);
// convert form field array's to Fuel dotted notation
$name = str_replace(array('[', ']'), array('.', ''), $field->name);
$value = $this->input($name);
if (($allow_partial === true and $value === null)
or (is_array($allow_partial) and ! in_array($field->name, $allow_partial)))
{
continue;
}
try
{
foreach ($field->rules as $rule)
{
$callback = $rule[0];
$params = $rule[1];
$this->_run_rule($callback, $value, $params, $field);
}
if (strpos($name, '.') !== false)
{
\Arr::set($this->validated, $name, $value);
}
else
{
$this->validated[$name] = $value;
}
}
catch (Validation_Error $v)
{
$this->errors[$field->name] = $v;
if($field->fieldset())
{
$field->fieldset()->Validation()->add_error($field->name, $v);
}
}
}
$validation->fields
を先頭から処理、fieldごとに$field->rules
を先頭から処理…という素直な構造。
要するに、addした順番に実行してくれるわけですね。そして、validatedに値が格納されるのは、そのfieldに定義されたバリデーションが全てパスしてから…となる。
カスタムバリデーションはどこでaddする?
フィールド内で閉じたバリデーションであれば、そのフィールドにadd_ruleすればOK。
複数フィールドを参照するものは、前提となるfield/ruleをaddし終えてからaddする。このとき、実際のfieldに対してaddするよりかは、バリデーション用のダミーのfieldを最後にaddして、そこにカスタムバリデーションをまとめてadd_ruleするのがいいかも?
実際にはFieldsetを介して操作することになるので、以下のような実装に落ち着いた。
$fieldset = Fieldset::forge('form_id');
$fieldset->add(/*field1*/)
->add(/*field2*/)
->add(/*field3*/)
;
$fieldset->validation()->add_callable('custom_validation_class');
$fieldset->validation()->add('dummy_field', 'label', [], [
['custom_validation1',/*'param1','param2',...*/],
['custom_validation2',/*'param1','param2',...*/],
]);
ちなみに、単一のフィールドに複数ルールをaddした場合、途中でエラーが検出されると以降のルールは処理されず、次のフィールドのバリデーションへ移行する。
ので、複数のカスタムバリデーションをaddするとき、それぞれのエラーメッセージを出したい場合は、ダミーのfieldを複数addして、それぞれにぶら下げる必要があるので注意。
このへん、ちょっとコードがダサいので、もう少し何とかならないものか…