アシアルブログ

アシアルの中の人が技術と想いのたけをつづるブログです

Piece Framework Listバリデータの拡張

こんばんは、前回のブログ本家のニュースで紹介され、ただただ恐縮している高橋です。

それにしても、一緒に紹介されているPieceCodeGeneratorの出来は素晴らしいですね。私の稚拙なスクリプトも、もっと使いやすくなるよう改良していきたいと思います。

さて、今回はPiece Frameworkでリスト項目のバリデート処理を行うときのことについて書いてみます。
登録フォームなどでリスト項目がある場合、アクションのphpで項目をテンプレートに割り付け、バリデーションのyamlにその検証リストを設定しますが、そのまま記述すると次のような感じになるかと思います。

アクションのphp


<?php
・・・
$elements = $this->_getFormElements();
$elements['fruit']['_options'] = array('apple'  => 'りんご',
                                       'banana' => 'バナナ',
                                       'cherry' => 'さくらんぼ'
                                       );
$viewElement =  &$this->_payload->getViewElement();
$viewElement->setElement('_elements', $elements);
・・・
?>


バリデーションのyaml


・・・
- name: fruit
  description: 果物
  validator:
    - name: List
      rule:
        elements: [apple, banana, cherry]
      message: リストにある%_description%を選択してください
・・・


このように記述した場合、リストの項目に変更があるとphpyamlの両方を修正する必要があるため、修正ミスが起こりやすいですね。
そこで、WithMethodを使用して、リスト項目を一元管理できるように変更してみます。

リスト項目のクラス


<?php
class MyFruit
{
    static $fruit = array('apple'  => 'りんご',
                          'banana' => 'バナナ',
                          'cherry' => 'さくらんぼ'
                          );

    static function checkFruit($value,  &$payload,  &$results) {
        if (isset(self::$fruit[$value])) {
            return true;
        } else {
            return false;
        }
    }
}
?>


アクションのphp


<?php
・・・
$elements = $this->_getFormElements();
$elements['fruit']['_options'] = MyFruit::$fruit;
$viewElement =  &$this->_payload->getViewElement();
$viewElement->setElement('_elements', $elements);
・・・
?>


バリデーションのyaml


・・・
- name: fruit
  description: 果物
  validator:
    - name: WithMethod
      rule:
        class: MyFruit
        method: checkFruit
      message: リストにある%_description%を選択してください
・・・


これでリスト項目に変更があっても、MyFruitだけ書き換えればいいので、ちょっと楽になりました。
ただ、新たに別のリスト項目が増えた場合、項目の配列だけでなくcheckFruit()と同様のメソッドも追加しないといけないので、そこがまだ面倒ですね。

そこで、ListとWithMethodを足して2で割ったようなList2.phpという独自バリデータを作ってみます。yamlの指定はelementsの代わりに、classとmemberでも配列の指定ができるものとします。

バリデーションのyaml


・・・
- name: fruit
  description: 果物
  validator:
    - name: List2
      rule:
        class: MyFruit
        member: fruit
      message: リストにある%_description%を選択してください
・・・


List2.php


<?php
・・・
function validate($value)
{
    if (!is_array($value)) {
        $value = (array)$value;
    }

    $class = $this->_getRule('class');
    $member = $this->_getRule('member');
    $isStatic = $this->_getRule('isStatic');
    if (is_null($class) || is_null($member)) {
        $elements = $this->getRule('elements');
    } else {
        if (!Piece_Right_ClassLoader::loaded($class)) {
            Piece_Right_ClassLoader::load($class, $this->_getRule('directory'));
            if (Piece_Right_Error::hasErrors('exception')) {
                return;
            }

            if (!Piece_Right_ClassLoader::loaded($class)) {
                Piece_Right_Error::push(PIECE_RIGHT_ERROR_NOT_FOUND,
                                        "The class [ $class ] not found in the loaded file.",
                                        'exception',
                                        array('validator' => __CLASS__)
                                        );
                return;
            }
        }

        if ($isStatic) {
            $elements = $class::$$member;
        } else {
            $instance =  &new $class();
            if (isset($instance->$member)) {
                return false;
            }
            $elements = $instance->$member;
        }
    }

    if (!is_array($elements)) {
        return false;
    }

    foreach ($value as $element) {
        if (!in_array($element, $elements)) {
            return false;
        }
    }

    return true;
}
・・・
function _initialize()
{
    $this->_addRule('elements', array());
    $this->_addRule('class');
    $this->_addRule('member');
    $this->_addRule('isStatic', true);
    $this->_addRule('directory');
}
・・・
?>


これで完成かなと思ったところ、「$elements = $class::$$member;」でシンタックスエラーが発生!
どうやらこうは書けない模様です。とはいえ、eval()ではreturn以外で値が取得できませんし、call_user_func()ではメソッドを書かずに済ますという当初の目的が果たせません。

あともう少しのところで、今回は時間切れ……。ううーん、何かいい方法はないものでしょうか?