Zend Framework: Validate Identical Field

AttachmentSize
ZExt_Validate_IdenticalField.php3.64 KB
zend_form_example.php1.39 KB

This little object is a canned and generic validator for Zend Framework. It is intended for use with Form Elements, but it should work fairly broadly. The basic need it addresses is to validate that one field value matches another (e.g. password/email and an associated validation field, a captcha challenge, etc.). I looked hard for a ready made answer to this issue, but after much searching and failed experimentation with other canned solutions I decided to write my OWN generic answer to the problem.

An example call to this function might look something like this; note especially the call to addElementPrefixPath():

<?php
/**
 * @category   Zend
 * @package    Zend_Form
 * @author     Sean P. O. MacCath-Moran
 * @email      zend@emanaton.com
 * @website    http://www.emanaton.com
 * @copyright  This work is licenced under a Attribution Non-commercial Share Alike Creative Commons licence
 * @license    http://creativecommons.org/licenses/by-nc-sa/3.0/us/
 *
*/
    
class PasswordForm extends Zend_Form {
  public function __construct($options = null) {
    parent::__construct($options);
    $this->setName('login');
    $this->addElementPrefixPath('ZExt_Validate', 'ZExt/Validate/', 'validate');
    
    $this->addElement(new Zend_Form_Element_Password('password'))->getElement('password')
      ->setLabel('Password')
      ->setRequired(true)
      ->addFilter('StripTags')
      ->addFilter('StringTrim')
      ->addValidator('NotEmpty')
      ->addValidator('IdenticalField', false, array('password_confirm', 'Confirm Password'))
    ;
    
    $this->addElement(new Zend_Form_Element_Password('password_confirm'))->getElement('password_confirm')
      ->setLabel('Confirm Password')
      ->setRequired(true)
      ->addFilter('StripTags')
      ->addFilter('StringTrim')
      ->addValidator('NotEmpty')
    ;
    
    $submitName = Zend_Registry::get('config')->const->form->button->submit->name;
    $this->addElement(new Zend_Form_Element_Submit($submitName))->getElement($submitName)
      ->setLabel('Save Password')
      ->setAttrib('id', Zend_Registry::get('config')->const->form->button->submit->id)
    ;
  }
}

Aaaaaand the valididator itself:

<?php
/**
 * @category   ZExt
 * @package    ZExt_Validate
 * @author     Sean P. O. MacCath-Moran
 * @email      zendcode@emanaton.com
 * @website    http://www.emanaton.com
 * @copyright  This work is licenced under a Attribution Non-commercial Share Alike Creative Commons licence
 * @license    http://creativecommons.org/licenses/by-nc-sa/3.0/us/
*/

require_once 'Zend/Validate/Abstract.php';

/**
 * @uses       ZExt_Validate_IdenticalField
 * @package    ZExt_Validate
 * @author     Sean P. O. MacCath-Moran
 * @email      zendcode@emanaton.com
 * @website    http://www.emanaton.com
 * @copyright  This work is licenced under a Attribution Non-commercial Share Alike Creative Commons licence
 * @license    http://creativecommons.org/licenses/by-nc-sa/3.0/us/
*/

class ZExt_Validate_IdenticalField extends Zend_Validate_Abstract {
  const NOT_MATCH = 'notMatch';
  const MISSING_FIELD_NAME = 'missingFieldName';
  const INVALID_FIELD_NAME = 'invalidFieldName';

  /**
   * @var array
  */
  protected $_messageTemplates = array(
    self::MISSING_FIELD_NAME  =>
      'DEVELOPMENT ERROR: Field name to match against was not provided.',
    self::INVALID_FIELD_NAME  =>
      'DEVELOPMENT ERROR: The field "%fieldName%" was not provided to match against.',
    self::NOT_MATCH =>
      'Does not match %fieldTitle%.'
  );

  /**
   * @var array
  */
  protected $_messageVariables = array(
    'fieldName' => '_fieldName',
    'fieldTitle' => '_fieldTitle'
  );

  /**
   * Name of the field as it appear in the $context array.
   *
   * @var string
   */
  protected $_fieldName;

  /**
   * Title of the field to display in an error message.
   *
   * If evaluates to false then will be set to $this->_fieldName.
   *
   * @var string
  */
  protected $_fieldTitle;

  /**
   * Sets validator options
   *
   * @param  string $fieldName
   * @param  string $fieldTitle
   * @return void
  */
  public function __construct($fieldName, $fieldTitle = null) {
    $this->setFieldName($fieldName);
    $this->setFieldTitle($fieldTitle);
  }

  /**
   * Returns the field name.
   *
   * @return string
  */
  public function getFieldName() {
    return $this->_fieldName;
  }

  /**
   * Sets the field name.
   *
   * @param  string $fieldName
   * @return Zend_Validate_IdenticalField Provides a fluent interface
  */
  public function setFieldName($fieldName) {
    $this->_fieldName = $fieldName;
    return $this;
  }

  /**
   * Returns the field title.
   *
   * @return integer
  */
  public function getFieldTitle() {
    return $this->_fieldTitle;
  }

  /**
   * Sets the field title.
   *
   * @param  string:null $fieldTitle
   * @return Zend_Validate_IdenticalField Provides a fluent interface
  */
  public function setFieldTitle($fieldTitle = null) {
    $this->_fieldTitle = $fieldTitle ? $fieldTitle : $this->_fieldName;
    return $this;
  }

  /**
   * Defined by Zend_Validate_Interface
   *
   * Returns true if and only if a field name has been set, the field name is available in the
   * context, and the value of that field name matches the provided value.
   *
   * @param  string $value
   *
   * @return boolean
  */
  public function isValid($value, $context = null) {
    $this->_setValue($value);
    $field = $this->getFieldName();

    if (empty($field)) {
      $this->_error(self::MISSING_FIELD_NAME);
      return false;
    } elseif (!isset($context[$field])) {
      $this->_error(self::INVALID_FIELD_NAME);
      return false;
    } elseif (is_array($context)) {
      if ($value == $context[$field]) {
        return true;
      }
    } elseif (is_string($context) && ($value == $context)) {
      return true;
    }
    $this->_error(self::NOT_MATCH);
    return false;
  }
}

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Thanks for the solution.

Thanks for the solution.

Why not simply use Zend_Controller_Front!!!?

$password = $this->createElement('password', 'password');
$password->setLabel('Mot de passe:');
$password->setRequired(true);
$password->addValidator(new Zend_Validate_StringLength(6,12));
$password->setAttrib('class', 'txt');
$this->addElement($password);

$confirmPassword = $this->createElement('password', 'confirm_password');
$confirmPassword->setLabel('Confirmer votre mot de passe:');
$confirmPassword->setRequired(true);
$confirmPassword->addValidator(new Zend_Validate_StringLength(6,12));
$confirmPassword->addValidator(new Zend_Validate_Identical(
Zend_Controller_Front::getInstance()->getRequest()->getParam('password')
));
$confirmPassword->setAttrib('class', 'txt');
$this->addElement($confirmPassword);

Re: Why not simply use Zend_Controller_Front!!!?

Greetings Omatcho,

Interesting approach! But why do you consider this method to be better?

Regards,

Sean P. O. MacCath-Moran
www.emanaton.com

There is one other solution

There is one other solution for that kind of problem while talking just about problematic 'Validate_Identical' validator:

<?php
class RegisterForm extends Zend_Form
{
    /** 
     * create your form
     */
    public function init()
    {
        $this->addElements(array(
            new Zend_Form_Element_Password('password',
                array( 'label' => 'Password:',
                           'required' => true,
                           'filters' => array('StringTrim', 'StripTags'),
                           'validators' => array(array(StringLength', false, array(5, 25)))
                )
            ),
            new Zend_Form_Element_Password('pass_twice',
                array('label' => 'Pass Twice',
                         'required' => true,
                         'filters' => array('StringTrim', 'StripTags'),
                         'validators' => array('Identical')
                )
            )
        );
    }
    
    public function isValid($data)
    {
        $passTwice = $this->getElement('pass_twice');
        $passTwice->getValidator('Identical')->setToken($data['password']);
        return parent::isValid($data);
    }
}
?>

So all your model logic stays in model and still not much to code. Otherwise you should do it in your controller or write your own validator, that seems not to be neccessary in this case.

Security issue?

One slight issue I've seen from looking at the shorter quick-fix version, is that if you fail validation, you will expose one of the two password entries in clear text on the screen. So take care when using this (this is with ZF 1.9)

Re: Security issue?

Greetings Chris B.,

Zoiks! I just verified this issue. Good note, thanks!

Regards,

Sean P. O. MacCath-Moran
www.emanaton.com

Reply to comment | emanaton

I'm now not certain where you are getting your information,
however great topic. I must spend a while studying more or working out more.
Thanks for great information I was looking for this info for
my mission.

Re: There is one other solution

Greetings Anonymous,

First, I must say that I love your poetry; you and that guy "Author Unknown" write some diverse and interesting material.

Second, thank you for this alternate approach to the problem. As you say, it is less code and it does consolidate the logic. For myself, I prefer the greater flexibility, general application opportunities, and code reuse offered by the class abstraction approach. However, if just banging something out quickly, I'll keep your approach in mind.

Thanks again!

Sean P. O. MacCath-Moran
www.emanaton.com

RE: There is one other solution

Thank you Anonymous,

Just to say that I really do love this way of doing things like this.

Save error messages into log files

Hi,

This is a great article, thanks!

I have a question:
when this-> error is called, is it possible to save the error information, such as user name/id, ip address, error message, etc, into a log file?
Can this be done in one function?

Thanks.

Re: Save error messages into log files

Greetings,

On first blush it occurs to me that one might achieve this by overwriting the Zend_Validate_Abstract->_createMessage function, perhaps in a custom implementation of Zend_Validate_Abstract, and using Zend_Log. Obviously, your validator would then extend this new abstract class instead of the current Zend_Validate_Abstract. The function in the class might look something like:

abstract class My_Validate_Abstract implements Zend_Validate_Abstract
{
  . . .
    /**
     * The logger to log messages to
     *
     * @var mixed
     */
    protected $_logger = false;
  . . .
    protected function _createMessage($messageKey, $value)
    {
        $message = parent::_createMessage($messageKey, $value);
        if ($this->_logger) {$this->_logger->log($message, Zend_Log::INFO);}
    }

    /**
     * Sets the logger object for this validator
     *
     * @param Zend_Log $logger
     */
    public function setLogger($logger)
    {
        // of course, one should type-check $logger instead of blindly accepting it
        $this->_logger = $logger;
    }
  . . .
}

Any translations to be run against the validation message are cared for in the parent::_createMessage call, making this (IMHO) a good place to insert such code. One could take a similar approach with the _error function... hmm... The problem there is that this function does not return the message added after translation (if that's important)... buuut you could log the message and then call parent function:

abstract class My_Validate_Abstract implements Zend_Validate_Abstract
{
  . . .
    protected function _error($messageKey = null, $value = null)
    {
        if ($this->_logger) {$this->_logger->log($value, Zend_Log::INFO);}
        parent::_error($messageKey, $value);
    }
  . . .
}

Make sense? Would anyone else care to weigh in on this?

Regards,

Sean P. O. MacCath-Moran
www.emanaton.com

Yes, it make sense. Thanks.

Yes, it make sense.

Thanks.

Zym Confirm

Greetings All,

I've recently discovered that the Zym Project has a validator implementation that is similar to mine. Although it provides fewer options (e.g. one may not set the title text for the sake of messages), it is quite functional and (as of the writing of this post) the project is well supported and in active development. Pick the one that works best for your needs. =o)

Regards,

Sean P. O. MacCath-Moran
www.emanaton.com

nice work

thanks for the validator! it works nicely.

Hi

I was about to write something like this myself.
This addresses a real problem with the Identical validator in the Zend_Form validation dynamic.
Maybe you should submit this to Zend. I suggest renaming to something more generic, as this doesn't have to be just for passwords.

re: Hi

Greetings Anonymous,

I may just submit this to Zend; it honestly hadn't occurred to me to do so, and I'll think on it.

As for a generic name, I believe you may have confused the example with the implementation, which is named "Zend_Validate_IdenticalField". =o)

Sean P. O. MacCath-Moran
www.emanaton.com

very helpful thanks

Good article Very helpful, thanks!!

------------

americans-away air jordan

Field not required loop hole....

First off, VERY NICE!

However, if the fields are not required the check doesn't always get triggered. If, for example, the fields were optional and 'password' was left empty the validation doesn't get triggered. You can get around this loophole by simply adding IdenticalField to both but then it is a little verbose when both have values and they don't match.

I have tried several ways to get around it but no luck.

Re: Field not required loop hole....

I think this can be solved by just adding an option to prevent the validator to return false if the field to compare with is not empty. I find it a bit hard to find a simple and logical name for this option, but I think $is2ndField will do.

Re: Field not required loop hole....

Greetings Anonymous,

This is an interesting point... However, I'm not sure what the use case is for an identical check against a non-required field. For now, I think I'll leave it as it is, but thank you VERY much for your input!

Sean P. O. MacCath-Moran
www.emanaton.com

Nice

Nice validator!

One suggestion, or maybe I am blind:

Having the fieldTitle translated with Zend_Translate producing nicer error messages.

Re: Nice

Greetings Anonymous,

The magic us actually just under the hood on this one. The Zend_Validate_Abstract takes care of providing an interface into Zend Translate, which provides the means to override the values of the error messages and to insert place holders as required... or did I miss understand your suggestion?

Sean P. O. MacCath-Moran
www.emanaton.com

GREAT

thank you very much!

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <pre>
  • Lines and paragraphs break automatically.
  • Use [fn]...[/fn] (or <fn>...</fn>) to insert automatically numbered footnotes.
  • Web page addresses and e-mail addresses turn into links automatically. (Better URL filter.)
  • Syntax highlight code surrounded by the {syntaxhighlighter SPEC}...{/syntaxhighlighter} tags, where SPEC is a Syntaxhighlighter options string or "class="OPTIONS" title="the title".

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.