PhGeek
PhGeek

Reputation: 13

How to use Zend_Validate twice

I'm using Zend_Validate_Db_NoRecordExists and I would like to validate my Zend_Form against two fields in DB, sample code:

    $phone_1 = new Zend_Form_Element_Text('phone_1');
    $phone_1->addValidator(new Zend_Validate_Db_NoRecordExists(
                            array(
                                'field' => 'phone_1',
                                'table' => 'customer',
                    )))
            ->addValidator(new Zend_Validate_Db_NoRecordExists(
                            array(
                                'field' => 'phone_2',
                                'table' => 'customer',
                    )));

Can I accomplish this?

Upvotes: 1

Views: 296

Answers (2)

BartekR
BartekR

Reputation: 3967

Create validation chain

$chain = new Zend_Validate();
$foo   = new Zend_Validate_Db_NoRecordExists(...);
$bar   = new Zend_Validate_Db_NoRecordExists(...);
$chain->addValidator($foo)
      ->addValidator($bar);

$element->addValidator($chain); 

UPDATE @Bob Kruithof

Quick and dirty sample using ZF 1.12.3. All code samples and line numbers are from ZF 1.12.3

<?php

$aPaths = array(
    'path/to/zf'
);

set_include_path(implode(PATH_SEPARATOR, $aPaths));

require_once 'Zend/Loader/Autoloader.php';
$autoloader = Zend_Loader_Autoloader::getInstance();

$element = new Zend_Form_Element_Text('testSO');

$chain = new Zend_Validate();
$bar = new Zend_Validate_Regex('/^Test/');
$baz = new Zend_Validate_Regex('/Valid$/');
$chain->addValidator($bar)
      ->addValidator($baz);

$element->addValidator($chain); 

echo PHP_VERSION . ', ' . Zend_Version::getLatest() . PHP_EOL;

var_dump($element->isValid('Test'));
var_dump($element->isValid('Valid'));
var_dump($element->isValid('Test Valid'));

/* output
5.3.8, 1.12.3
bool(false)
bool(false)
bool(true)
*/

?>

Why this works: Zend_Form_Element.php lines 1144 - 1168 sets validators either by Zend_Validate object or by string. You are right - if validators are used with name, not as chain, then as a result one would have single validator. But in result we have:

$this->_validators[$name] = $validator;
/* debugger info:
    $name --> (string) Zend_Validate
    $validator --> Zend_Validate object {
  _validators => array(2) (
    [0] => array(2) (
      [instance] => Zend_Validate_Regex object {
      }
      [breakChainOnFailure] => (bool) false
    )
    [1] => array(2) (
      [instance] => Zend_Validate_Regex object {
      }
      [breakChainOnFailure] => (bool) false
    )
  )
  _messages => array(0)
  _errors => array(0)
  zfBreakChainOnFailure => (bool) false
}
*/

public function addValidator($validator, $breakChainOnFailure = false, $options = array())
    {
        if ($validator instanceof Zend_Validate_Interface) {
            $name = get_class($validator);

            if (!isset($validator->zfBreakChainOnFailure)) {
                $validator->zfBreakChainOnFailure = $breakChainOnFailure;
            }
        } elseif (is_string($validator)) {
            $name      = $validator;
            $validator = array(
                'validator' => $validator,
                'breakChainOnFailure' => $breakChainOnFailure,
                'options'             => $options,
            );
        } else {
            require_once 'Zend/Form/Exception.php';
            throw new Zend_Form_Exception('Invalid validator provided to addValidator; must be string or Zend_Validate_Interface');
        }


        $this->_validators[$name] = $validator;

        return $this;
    }

When we call $element->isValid()) it iterates over all validators inside of the chain - check Zend_Validate.php, lines 91-110:

public function isValid($value)
    {
        $this->_messages = array();
        $this->_errors   = array();
        $result = true;
        foreach ($this->_validators as $element) {
            $validator = $element['instance'];
            if ($validator->isValid($value)) {
                continue;
            }
            $result = false;
            $messages = $validator->getMessages();
            $this->_messages = array_merge($this->_messages, $messages);
            $this->_errors   = array_merge($this->_errors,   array_keys($messages));
            if ($element['breakChainOnFailure']) {
                break;
            }
        }
        return $result;
    }

$this->_validators is our array of validators:

array(2) (
  [0] => array(2) (
    [instance] => Zend_Validate_Regex object {
      _messageTemplates => array(3) (
      )
      _messageVariables => array(1) (
      )
      _pattern => (string) /^Test/
      _value => null
      _messages => array(0)
      _obscureValue => (bool) false
      _errors => array(0)
      _translator => null
      _translatorDisabled => (bool) false
    }
    [breakChainOnFailure] => (bool) false
  )
  [1] => array(2) (
    [instance] => Zend_Validate_Regex object...

Upvotes: 2

eelco
eelco

Reputation: 218

Zend will add the validators to your form element in an array using the classname of the validator as key. This means that if you want to add the same validator twice but with different specifications, the second validator will overwrite the first.

The most easy, and only slightly messy, way to accomplish adding the same validator is to extend the validator so the second validator receives a different classname:

class NewValidator extends Zend_Validate_Db_NoRecordExists { }

Now you can use the validator twice:

$phone_1->addValidator(new Zend_Validate_Db_NoRecordExists(
                        array(
                            'field' => 'phone_1',
                            'table' => 'customer',
                )))
        ->addValidator(new NewValidator(
                        array(
                            'field' => 'phone_2',
                            'table' => 'customer',
                )));

This will do the trick and is a lot faster then creating a custom validator.

Upvotes: 1

Related Questions