Custom validators for Zend_Form_Element

I am checking out Zend_Form from the Zend Framework these days and on my way to work today I wrote a custom validator that checks if an element has the same value as other elements in the form (typically used to confirm a password or an email address).

This “tutorial” contains quite a lot of code and requires some understanding of how Zend Framework works and how OOP works in PHP5.

My “register user” form consists of four elements:

  • Email
  • Confirm email
  • Password
  • Confirm password

All elements are required and I have attached the EmailAddress validator to the two email elements to ensure correct addresses. The thing I also want to do is to ensure that the two email elements have the same value. The same goes for the two password elements. To do this I had to write a custom validator for these cases. A similar validator can be found in the Zend_Form docs but I wanted to be able to specify the names of the element(s) an element must be equal to when I add the validator instead of hardcoding them in the validator classes (like the example in the docs does).

The form class looks a little something like this:

<?php
require_once 'Zend/Form.php';

class FKK_RegisterUserForm extends Zend_Form {
    public function init() {
        /* Set the action and method. The action is generated using the view's url helper */
        $this->setAction($this->getView()->url(array(), 'User_Register'));
        $this->setMethod('post');

        $mail = new FKK_Form_Element_Text('mail');
        $mail->setLabel('Mail');
        $mail->setDescription('Your email address');
        $mail->setRequired(true);
        $mail->addValidator('EmailAddress', true);
        $mail->addValidator('EmailConfirmation', false, array('mailRepeat'));

        $mailRepeat = new FKK_Form_Element_Text('mailRepeat');
        $mailRepeat->setLabel('Mail (repeat)');
        $mailRepeat->setDescription('Please confirm your email address');
        $mailRepeat->setRequired(true);
        $mailRepeat->addValidator('EmailAddress', true);
        $mailRepeat->addValidator('EmailConfirmation', false, array('mail'));

        $password = new FKK_Form_Element_Password('password');
        $password->setLabel('Password');
        $password->setDescription('Your password. Must be between 4 and 20 characters.');
        $password->setRequired(true);
        $password->addValidator('StringLength', true, array(4, 20));
        $password->addValidator('PasswordConfirmation', false, array('passwordRepeat'));

        $passwordRepeat = new FKK_Form_Element_Password('passwordRepeat');
        $passwordRepeat->setLabel('Password (repeat)');
        $passwordRepeat->setDescription('Please confirm your password');
        $passwordRepeat->setRequired(true);
        $passwordRepeat->addValidator('StringLength', false, array(4, 20));
        $passwordRepeat->addValidator('PasswordConfirmation', false, array('password'));

        $submit = $this->createElement('submit', 'registerUser');
        $submit->setLabel('Register user');

        $this->addElements(array($mail, $mailRepeat, $password, $passwordRepeat, $submit));
    }
}

The class is just an extension of the Zend_Form class. The only method the class needs to implement is a public init() method which is called from Zend_Form’s constructor.

The init() method above creates some custom text (FKK_Form_Element_Text) and password (FKK_Form_Element_Password) elements. The reason I have made my own elements is so I can do specific stuff with only text and password fields (like adding the Description decorator and a custom plugin prefix. More on this later).

After creating an element I set some properties to it and then add some validators. When adding multiple validators I specify the second argument to the addValidator method ($breakChainOnFailure) to stop the rest of the validation when one of them fails. At least thats what I think $breakChainOnFailure does. :)

The interesting validator here is “EmailConfirmation” and “PasswordConfirmation”. They both to pretty much the same thing so I’ll only describe one of them.

$mail->addValidator('EmailConfirmation', false, array('mailRepeat'));

The code above means that the mail element must have the same value as the “mailRepeat” element. If there are other elements it needs to match as well you can just specify more element names in the array. The third parameter to the addValidator method is passed to the constructor of the validator which will be shown later on. Lets move on!

The custom element classes looks like this:

<?php
require_once 'Zend/Form/Element/Text.php';

class FKK_Form_Element_Text extends Zend_Form_Element_Text {
    public function init() {
        $this->setDisableLoadDefaultDecorators(true);
        $this->addPrefixPath('FKK_Validate', 'FKK/Validate/', 'validate');

        $this->addDecorator('ViewHelper');
        $this->addDecorator('Errors');
        $this->addDecorator('Description', array('escape' => false, 'class' => 'fieldDescription'));
        $this->addDecorator('HtmlTag', array('tag' => 'dd'));
        $this->addDecorator('Label', array('requiredSuffix' => ' *', 'tag' => 'dt', 'class' => 'fieldLabel'));
    }
}

The FKK_Form_Element_Password class is exactly the same except that it extends the Zend_Form_Element_Password class instead so I’ll skip that one.

First I disable the loading of the default decorators and then add a custom prefix path that is used by the plugin loader (Zend_Loader_PluginLoader) that loads up the different decorators and validators. I need to do this so I can magically use my own validators.

Last I add the decorators I want and configure them the way I want them to appear. Most of the stuff is pretty close to the default (with the exception of the Description decorator). As you might notice I only override the public init() method (as I did when extending Zend_Form). The init() method is called from the constructor inherited from Zend_Form_Element_Text and Zend_Form_Element_Password.

And now onto the interesting part … the FKK_Validate_EmailConfirmation validator (as mentioned the PasswordConfirmation validator works exactly the same way so I only cover one of them).

The class looks a little something like this:

<?php
require_once 'Zend/Validate/Abstract.php';

class FKK_Validate_EmailConfirmation extends Zend_Validate_Abstract {
    const NOT_MATCH = 'emailConfirmationNotMatch';

    protected $_messageTemplates = array(
        self::NOT_MATCH => 'Email confirmation does not match'
    );

    /**
     * The fields that the current element needs to match
     *
     * @var array
     */
    protected $_fieldsToMatch = array();

    /**
     * Constructor of this validator
     *
     * The argument to this constructor is the third argument to the elements' addValidator
     * method.
     *
     * @param array|string $fieldsToMatch
     */
    public function __construct($fieldsToMatch = array()) {
        if (is_array($fieldsToMatch)) {
            foreach ($fieldsToMatch as $field) {
                $this->_fieldsToMatch[] = (string) $field;
            }
        } else {
            $this->_fieldsToMatch[] = (string) $fieldsToMatch;
        }
    }

    /**
     * Check if the element using this validator is valid
     *
     * This method will compare the $value of the element to the other elements
     * it needs to match. If they all match, the method returns true.
     *
     * @param $value string
     * @param $context array All other elements from the form
     * @return boolean Returns true if the element is valid
     */
    public function isValid($value, $context = null) {
        $value = (string) $value;
        $this->_setValue($value);

        $error = false;

        foreach ($this->_fieldsToMatch as $fieldName) {
            if (!isset($context[$fieldName]) || $value !== $context[$fieldName]) {
                $error = true;
                $this->_error(self::NOT_MATCH);
                break;
            }
        }

        return !$error;
    }
}

The validator extends the Zend_Validate_Abstract class and contains two methods:

  • public function __construct($fieldsToMatch = array())
  • public function isValid($value, $context = null)

The constructor is needed so we can store the parameter that is given to the addValidator() method (see above).

The isValid() method simply compares the value of the current element with the values of the other elements it needs to match. The $context parameter is an associative array that contains all the other elements in the form.

The error message follows the same pattern as the other validate classes so we can use Zend_Translate to have different translations of the error message (not covered here).

The only thing left to do now is to display the form and when validating using $form->isValid($_POST); the custom validator will run and display an error next to the fields that is not valid. The following code will do that:

<?php
// Create an instance of the form
$form = new FKK_RegisterUserForm();

// Initialize the array of values that we will populate the form with
$values = array();

// See if anyone has clicked the submit button
if (isset($_POST&#91;'registerUser'&#93;)) {
    $values = $form->getValues();

    // see if the form is valid
    if ($form->isValid($_POST)) {
        // do some clever stuff here
    }
}

// Populate the form with values
$form->populate($values);

// Display the form
print($form);

And thats about it. :) Please leave comments and if you see any mistakes I made don’t hesitate to tell me about it. :) I am new to Zend_Form so there are probably some stuff that can be done in a different (read: better) way.

Advertisements
This entry was posted in PHP, Technology and tagged , , . Bookmark the permalink.

41 Responses to Custom validators for Zend_Form_Element

  1. Martin says:

    I don’t see the point to check if field1 equals to field2 and then again if field2 equals to field1. Waste of resources :-) Otherwise nice tutorial! :-)

  2. christer says:

    It’s mostly to get the same error message on both elements when rendering the form. I could probably solve that in some other way to skip the validator in the second element if it failed the first time (and vice versa)

  3. Pingback: Zend Framework in Action » Custom validators for Zend_Form_Element

  4. Pingback: Mats Lindh

  5. Angelo says:

    Why????
    Fatal error: Uncaught exception ‘Zend_Loader_PluginLoader_Exception’ with message ‘Plugin by name EmailConfirmation was not found in the registry.’ in

  6. christer says:

    @Angelo: Did you remember the following line:

    $this->addPrefixPath('FKK_Validate', 'FKK/Validate/', 'validate');
    

    If not the PluginLoader might not find your custom validators.

  7. Angelo says:

    Yes!
    I have added this “$this->addPrefixPath(‘My_Validate’, ‘My/Validate/’, ‘validate’);” on My/Form/Element/Text.php

  8. Angelo says:

    Don’t work! Please make the download file for this tutorial

  9. christer says:

    @angelo: Are you sure that the the “My” directory is in your include_path and that your validator is correctly named?

    If your EmailConfirmation validator is located in /path/to/libs/My/Validate/EmailConfirmation.php you have to make sure /path/to/libs is in your include_path.

    I’m sorry but I don’t have something prepared for download for this tutorial.

  10. Angelo says:

    Yes, i have added “My” in library with Zend, and i have in index.php this set_include_path(
    ‘.’ . PATH_SEPARATOR .
    ‘./library’ . PATH_SEPARATOR .
    ‘./app/models/’ . PATH_SEPARATOR .
    ‘./app/forms/’ . PATH_SEPARATOR .
    get_include_path()
    );

  11. Pingback: Translating Zend_Form error messages and more « Christer’s blog o’ fun

  12. Jason Qi says:

    Great Tutorial! For improvement,I’d like to make one custom validate class instead of two.

    Here is just a quick modification:

    1) class FKK_Validate_Match extends Zend_Validate_Abstract

    2) line 6 change to
    const NOT_MATCH = ‘NotMatch’;

    3) Between line 54 and 55, add

    $errorMessage = $fieldName . ‘ does not match’;
    $this->setMessage($errorMessage, self::NOT_MATCH);

    line 7 & 8 become useless.

    Thus, you can apply this to both email and password to validate their match.

    Further, I guess it should be a standard validator in Zend Framework.

  13. christer says:

    @Jason: Actually I do have a generic match validator myself but split it in two for the sake of the tutorial. I agree with you about it being a standard validator in ZF. There are plenty of forms who needs something like this.

  14. Pingback: Zend Framework How To: Creating your own validator for confirm passwords and emails | eKini: Web Developer Blog

  15. wenbert says:

    Where do I put this file?
    # class FKK_Validate_EmailConfirmation extends Zend_Validate_Abstract {

  16. christer says:

    @wenbert: I have it inside my library folder using the same class/filename scheme as Zend Framework:

    library/FKK/Validate/EmailConfirmation.php

    If you make sure the library directory is in the include path you should be fit to go!

  17. wenbert says:

    @christer: awesome! :) thanks so much!

  18. Greg says:

    Pretty nice. I have this working fine when I use a custom Zend_Form. However, I cannot get it to work when building my form from a config/ini file. I keep getting the same error as post #5, that the validator plugin cannot be found. Any suggestions on what to add where so that the prefix path is found correctly?

  19. Greg says:

    Ha! Never mind. The documentation on http://framework.zend.com/manual/en/zend.form.forms.html#zend.form.forms.config is incorrect. I have filed a bug. All the *refixPaths should be singular “prefixPath”

  20. Rohit says:

    Hi i have following error when i try to run above code,will you help me
    Uncaught exception ‘Zend_Form_Exception’ with message ‘Invalid type “VALIDATE” provided to getPluginLoader()’

  21. christer says:

    @Rohit: I need some more information to help you with this. What version of ZF are you using?

    I’m guessing it’s the addPrefixPath in the different elements that causes the error. I have alimost the same code as above in a project that uses ZF-1.5.2 and it works fine.

    If you follow the stack trace you can see what causes the error…

  22. Daniel says:

    Thank you so much for this article. It took some time but once I wrapped my head around the addPrefixPath method I was able to get it to work. I did not use an extended Zend_Form_Element though. I simply did the following in my form’s init method:

    // add custom validators
    $elements = $this->getElements();
    $confirmPassword = $elements[‘confirmPassword’];
    $confirmPassword->addPrefixPath(‘My_Validate’, ‘My/Validate/’, ‘validate’);
    $confirmPassword->addValidator(‘EmailConfirmation’, false, array(‘mailRepeat’));

  23. Crungmungus says:

    Shouldn’t it be:

    addElementPrefixPath() ?

  24. Strick says:

    I made one StringEqual class and wanted to pass a custom message through it, but couldn’t, I ended up just making two subclasses:

    class Validate_EmailConfirmation extends Validate_StringEqual {

    protected $_messageTemplates = array(
    self::NOT_MATCH => ‘Email confirmation does not match’
    );

    }

    and

    ‘Password confirmation does not match’
    );

    }

    I couldn’t figure out a way to set the self::NOT_MATCH through a new construct parameter. Any suggestions?

    Any tips on how to just send a parameter

  25. Pingback: 2008 is almost at an end « Christer’s blog o’ fun

  26. unixvps says:

    good article to understand how validation works in zend framework!

  27. Zach says:

    I had trouble getting it working without my own class… eventually just stuck it in the Zend folder…

    Then I realised that I’ll absolutely need my own class for each element so I went ahead and made them (well, started) and its working great. Probably should toss that out there that you basicly DO want to make your own class, no matter what. Base elements are… ugly!

  28. Pingback: Zend_Form custom validation - Zend Framework Forum

  29. codeskrat says:

    I had trouble getting this working, but eventually found my bug. I made the mistake of using a directory named ‘validate’ for my custom validators. This caused my addPrefixPath to look like:
    $this->addPrefixPath(‘Validate’, ‘Validate/’, ‘validate’);

    This had the bad side effect of looking for all the default Zend validators in my ../Validate/ directory.

    The fix is to make sure your custom validators have a different/unique name/path. In the above example, the ‘FKK_’ makes the path unique, and avoids my problem:
    $this->addPrefixPath(‘FKK_Validate’, ‘FKK/Validate/’, ‘validate’);

    I renamed my Validate directory to CustomValidate, and all is well:

    $this->addPrefixPath(‘FKK_Validate’, ‘FKK/Validate/’, ‘validate’);

    Thanks for the tutorial!

  30. Great article. I’ll be reworking some similar code tonight when I get home.

  31. Maarten says:

    Thanx for this solution I was desperately looking for. Gonna bookmark this blog, by the way!

  32. Pingback: 網站製作學習誌 » [Web] 連結分享

  33. Habib says:

    Thanks! I was looking exactly for this! The documentation did not help how to pass in the parameters, but this tutorial helped a lot!

  34. Rhys says:

    I find Zend to be very difficult to find good resources/plugins for (compared to, say, jQuery), so it’s been refreshing to find this. I just changed the namespace to the one I’m using and it worked straight away, so thanks a lot for your efforts.

  35. Worked like a champ! Thanks!

  36. Morris Lee says:

    Why I am getting this error?
    I guess the path is wrong.
    Sorry I am a newbe

    Fatal error: Uncaught exception ‘Zend_Loader_PluginLoader_Exception’ with message ‘Plugin by name ‘EmailConfirmation’ was not found in the registry; used paths: Zend_Validate_: Zend/Validate/’ in D:\ZendServer\ZendServer\share\ZendFramework\library\Zend\Loader\PluginLoader.php:406 Stack trace: #0 D:\ZendServer\ZendServer\share\ZendFramework\library\Zend\Form\Element.php(2063): Zend_Loader_PluginLoader->load(‘EmailConfirmati…’) #1 D:\ZendServer\ZendServer\share\ZendFramework\library\Zend\Form\Element.php(1247): Zend_Form_Element->_loadValidator(Array) #2 D:\ZendServer\ZendServer\share\ZendFramework\library\Zend\Form\Element.php(1320): Zend_Form_Element->getValidators() #3 D:\ZendServer\ZendServer\share\ZendFramework\library\Zend\Form.php(2036): Zend_Form_Element->isValid(NULL, Array) #4 D:\___Development\Home\contactus.php(18): Zend_Form->isValid(Array) #5 {main} thrown in D:\ZendServer\ZendServer\share\ZendFramework\library\Zend\Loader\PluginLoader.php on line 406

  37. suminho says:

    same here…

  38. Pingback: Wanderson Camargo » Blog Archive » Validar Campos Dependentes no Zend_Form

  39. Half says:

    It works for me.

    $new_username = new Zend_Form_Element_Text(‘new_username’);
    $new_username->addPrefixPath(‘Validator’, ‘Validator/’, ‘validate’);
    $new_username->addValidator(‘FormUserValideUsername’)

    My class is at ZEND_ROOT_APP_PATH/library/Validator/FormUserValideUsername.php

  40. Pingback: ¿Cómo crear una validación personalizada en Zend? | Swapbytes

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s