あーさーの備忘録

ゆっくり自由に生きてます

FuelPHPのValidationにクロージャを複数使う

FuelPHPのValidationにクロージャを使う

FuelPHPでは、クラスを作りメソッドを定義することでオリジナルのValidationルールを作ることができます。ルールに対するメッセージ文も、APPPATH/langのファイルを編集することで定義できます。でも、あるページだけでしか使わないルールをいちいち作るのは面倒。

ということで、Validationインスタンスごとルールを追加できる方法の登場です。

qiita.com

普通は上に挙げた記事通りやれば上手くいくのですが……

クロージャを複数使った場合の挙動

上の記事では、Validation::add_rule()にクロージャを渡すことによって独自ルールを定義しています。これを複数回行ったときに、ある不具合が生じます。

たとえば、アカウント作成フォームを作るとしましょう。

アカウント作成フォームに求められる要件は以下の通りです。

  • IDがユニークであること
  • メールアドレスがユニークであること

同じIDで複数回登録できたら区別がつかなくなってしまいますからね。

ということで、前述の記事を参考に、以下のようにルールを作ったとします。(本当はIDにvalid_stringがあったりするのでしょうが必要最低限ということで)

<?php
$val = Validation::forge();
$val->add('id', 'ID')
    ->add_rule('required')
    ->add_rule(function($id) {
        $record = DB::select()
            ->from('users')
            ->where('id', '=', $id)
            ->execute()
            ->as_array();
        if ($record !== []) {
            Validation::active()->set_message('closure', 'すでに同IDのユーザーが存在します。');
            return false;
        }
        return true;
    });
$val->add('email', 'メールアドレス')
    ->add_rule('required')
    ->add_rule(function($email) {
        $record = DB::select()
            ->from('users')
            ->where('email', '=', $email)
            ->execute()
            ->as_array();
        if ($record !== []) {
            Validation::active()->set_message('closure', 'このメールアドレスはすでに登録されています。');
            return false;
        }
        return true;
    });

ちなみに、Validation::active()を使っているのはスコープの関係です。useで$valを渡せば動くんじゃないかな(未検証)。

このようにすると上手くいくように思えますが、両方のValidationに引っかかった場合、後のメッセージが前のメッセージを上書きしてしまい、同じメッセージが2回表示されてしまいます。この方法ではset_message()の際にクロージャを区別できないというわけです。

解消方法

困ったのでドキュメントを読んだところ、下記ページにこんな記述がありました。

Validation Errors - Classes - FuelPHP Documentation

If you want to give them custom names instead you can do that like

<?php
// Add a rule which checks if the input is odd
// It can either use ->set_message('odd', ':label is not odd.') or use a lang key 'validation.odd'
$field->add_rule(array('odd' => function($val) { return (bool) ($val % 2); }));

先ほどクロージャを渡していたところに、array((ルール名) => (クロージャ))を渡すことでルールに名前をつけることができるということですね。

これを踏まえて先ほどのコードを書き換えます。

<?php
$val = Validation::forge();
$val->add('id', 'ID')
    ->add_rule('required')
    ->add_rule(['unique_id' => function($id) {
        $record = DB::select()
            ->from('users')
            ->where('id', '=', $id)
            ->execute()
            ->as_array();
        if ($record !== []) {
            Validation::active()->set_message('unique_id', 'すでに同IDのユーザーが存在します。');
            return false;
        }
        return true;
    }]);
$val->add('email', 'メールアドレス')
    ->add_rule('required')
    ->add_rule(['unique_email' =>function($email) {
        $record = DB::select()
            ->from('users')
            ->where('email', '=', $email)
            ->execute()
            ->as_array();
        if ($record !== []) {
            Validation::active()->set_message('unique_email', 'このメールアドレスはすでに登録されています。');
            return false;
        }
        return true;
    }]);

こうすると、クロージャ同士がメッセージを上書きせずに済みます。

ちなみに、この方法ならメッセージをクロージャ内で定義する必要がなく、クロージャの外側で$val->set_message()としてあげるともう少し綺麗に書けるのかなと思います。というか元の方法でも、クロージャが1つしかないのなら外側で定義できる……はず。

こういう技術系記事ってQiitaでやったほうがいいのかもしれないね。