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:
- 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['registerUser'])) {
$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.




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! :-)
Comment by Martin — April 16, 2008 @ 3:09 pm
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)
Comment by christer — April 16, 2008 @ 3:33 pm
[...] Christer has written a tutorial on writing custom validators for Zend_Form_Element. [...]
Pingback by Zend Framework in Action » Custom validators for Zend_Form_Element — April 18, 2008 @ 1:33 pm
Writing a Custom Validator for Zend_Form_Element…
My good friend Christer has written a simple tutorial on how to write a custom validator for a Zend_Form_Element. If you’ve ever laid your hands on Zend_Form, you’ll want to have a look at this for a short and concise introduction to the topic. He’l…
Trackback by Mats Lindh — April 18, 2008 @ 9:02 pm
Why????
Fatal error: Uncaught exception ‘Zend_Loader_PluginLoader_Exception’ with message ‘Plugin by name EmailConfirmation was not found in the registry.’ in
Comment by Angelo — April 23, 2008 @ 2:10 am
@Angelo: Did you remember the following line:
$this->addPrefixPath('FKK_Validate', 'FKK/Validate/', 'validate');If not the PluginLoader might not find your custom validators.
Comment by christer — April 23, 2008 @ 9:37 am
Yes!
I have added this “$this->addPrefixPath(’My_Validate’, ‘My/Validate/’, ‘validate’);” on My/Form/Element/Text.php
Comment by Angelo — April 23, 2008 @ 4:24 pm
Don’t work! Please make the download file for this tutorial
Comment by Angelo — April 23, 2008 @ 5:08 pm
@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.
Comment by christer — April 23, 2008 @ 7:27 pm
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()
);
Comment by Angelo — April 23, 2008 @ 7:37 pm
[...] This is simply a text element with a validator attached. Yes, I know this is completely overkill but it might help you understand more on how to make custom elements. The validator could of course be added to the element in the form class, but hey, you already know how to do that! If not, have a look at my previous post on Zend_Form! [...]
Pingback by Translating Zend_Form error messages and more « Christer’s blog o’ fun — April 24, 2008 @ 4:37 pm
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.
Comment by Jason Qi — April 24, 2008 @ 10:03 pm
@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.
Comment by christer — April 25, 2008 @ 8:46 am
[...] I found this. It has more or less a complete coverage with sample source-code and some explanation. My [...]
Pingback by Zend Framework How To: Creating your own validator for confirm passwords and emails | eKini: Web Developer Blog — May 14, 2008 @ 10:55 am
Where do I put this file?
# class FKK_Validate_EmailConfirmation extends Zend_Validate_Abstract {
Comment by wenbert — May 14, 2008 @ 11:02 am
@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!
Comment by christer — May 14, 2008 @ 1:27 pm
@christer: awesome! :) thanks so much!
Comment by wenbert — May 14, 2008 @ 6:54 pm
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?
Comment by Greg — June 11, 2008 @ 4:22 pm
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”
Comment by Greg — June 11, 2008 @ 4:38 pm
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()’
Comment by Rohit — June 23, 2008 @ 9:00 am
@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…
Comment by christer — June 23, 2008 @ 9:51 am