バリデータをチェーンするmyChainValidator

よくあるパターンとして

・パスワード入力フォームで、パスワードを変更する場合はチェックをいれてパスワード入力欄に入力する

というとき

<input type="checkbox" name="change_password" value="1" /> パスワードを変更する <br />
<input type="password" name="password1" />
<input type="password" name="password2" /> ※確認用

という感じのフォームになると思うのですが、この場合バリデータで検証させるルールは

change_password
  │
  ├→ チェックなし → true
  └→ チェックあり
      ↓
   パスワードチェック
      │
      ├― passwrod1 ┬→ 入力なし → false
      |       └→ 入力あり
      |           ↓
      └― password2 ─→ 同一性チェック ┬ 一致しない → false
                        └ 一致する  → true

という流れになると思います。

symfonyデフォルトのバリデータだけではymlでは表現しきれない(ActionのvalidateXXXではできますが)ので以下のようなカスタムバリデータを用意。

<?php

class myChainValidator extends sfValidator
{
  public function initialize ($context, $parameters = null)
  {
    parent::initialize($context);
    $this->setParameter('chain_error', 'chain error');
    $this->getParameterHolder()->add($parameters);
  }

  public function execute (&$value, &$error)
  {
    if (!is_array($this->getParameter('chain'))) return false;
    $_params = array(
      'required'     => false,
      'required_msg' => 'Required' ,
      'group'        => null ,
    );
    $_ignore = explode(',', 'required,required_msg,group');
    $context = $this->getContext();
    $v = new sfValidatorManager();
    $v->initialize($context);
    foreach ($this->getParameter('chain') as $name => $params)
    {
      $params = array_merge($_params, $params);
      $v->registerName(
        $name ,
        $params['required'] ,
        (isset($params['required']['msg'])) ? $params['required']['msg'] : $params['required_msg'] ,
        null ,
        $params['group'] ,
        false
      );
      foreach (array_keys($params) as $validator)
      {
        if (in_array($validator, $_ignore)) continue;
        $_validator = new $validator;
        $_validator->initialize($context, $params[$validator]);
        $v->registerValidator($name, $_validator);
      }
    }
    $v->execute();
    return true;
  }
}

バリデータ用のymlで

fields:
  change_password:
    myChainValidator:
      chain:
        password1:
          required:
            msg: 入力されていません
        password2:
          sfCompareValidator:
            check:         password1
            compare_error: 2つのバスワードが一致しません

と書けばとりあえず期待どおり動きます。

sfValidatorManagerのインスタンス作ったりしてるのであまり行儀は良くないけど割と使えます。