I am trying to validate Zend_Form which has several optional fields and I want at least one of them to be filled in. In my case I have mobile, home and office phone numbers and I want at least one of them to be provided.
I am trying to achieve this though Validation Context (as suggested here) by creating custom validator which extends Zend_Validate_Abstract. The problem is that if all optional fields are empty they are missing from the form $context (passed to the validator class) and this way not validated at all.
So if you fill any or several of the three options (mobile, home, work) they are all going to be validated (which is fine, but for this no custom validator is needed), but if you fill none of them, there is no option to force the customer to fill at least one of the fields (which is my aim).
Here is what I have:
1. my form
<?php
class Application_Form_Application extends Zend_Form {
public function init() {
$this->setName('application');
// attach sub forms to main form
$this->addSubForms(array(
'application' => $this->application(),
...
));
}
private function application() {
$application = new Zend_Form_SubForm();
// custom phone validation
$phone_validation = array('phone_mobile' => 'Mobile', 'phone_home' => 'Home', 'phone_work' => 'Work');
// phone mobile
$app['phone_mobile'] = new Zend_Form_Element_Text('phone_mobile');
$app['phone_mobile']->setLabel('Mobile')
->addFilter('StripTags')
->addFilter('StringTrim')
->addValidator('Regex', false, array('/^[0-9]{8}$/i'))
->addValidator(new Application_Form_PhoneMobileHomeWork($phone_validation), false);
// phone home
$app['phone_home'] = new Zend_Form_Element_Text('phone_home');
$app['phone_home']->setLabel('Home')
->addFilter('StripTags')
->addFilter('StringTrim')
->addValidator('Regex', false, array('/^[0-9]{8}$/i'))
->addValidator(new Application_Form_PhoneMobileHomeWork($phone_validation), false);
// phone work
$app['phone_work'] = new Zend_Form_Element_Text('phone_work');
$app['phone_work']->setLabel('Work')
->addFilter('StripTags')
->addFilter('StringTrim')
->addValidator('Regex', false, array('/^[0-9]{8}$/i'))
->addValidator(new Application_Form_PhoneMobileHomeWork($phone_validation), false);
$application->AddElements($app);
}
}
?>
2. custom validator
<?php
class Application_Form_PhoneMobileHomeWork extends Zend_Validate_Abstract {
const NOT_PRESENT = 'notPresent';
protected $_messageTemplates = array(
self::NOT_PRESENT => 'At least one contact phone shall be provided!'
);
protected $_listOfFields;
public function __construct(array $listOfFields) {
$this->_listOfFields = $listOfFields;
var_dump($listOfFields);exit;
}
public function isValid($value, $context = null) {
var_dump($context);exit;
...
}
?>
The validator always passes though the first dump ($listOfFields), but if I remove it, isValid() is never called unless some data is typed into some of the phone fields (which we want to prevent).
When I checked further I found a solution in extending the Zend_Validate class by passing empty fields to the $context parameter, but I would like to have a better solution if someone knows any.
Concluding it in short - how to validate certain form, forcing the user to fill at least one out of several optional fields?
If I understand you right, you want your form elements to not be required, but prevent them to be empty (except if one of them is not empty) using a custom validator? Then, in order to not skip the validation chain, you need to prevent them to be empty calling the method setAllowEmpty(false) in each of your elements.
Finally, in your custom validator, you will have something like this:
foreach ($this->_listOfFields as $field) {
if (isset($context[$field]) AND $context[$field])
{
return true;
}
}
Also, make sure your elements are not required (setRequired(false)).
The problem is that if any field is filled, the multi_checkbox element doesn't exists in the form, and then it won't be validated.
One solution is the follow:
Use a hidden option always checked and validate that this always is checked this more one of the others.
Related
I have a form (defined in XML) which is used with Joomla's JForm to handle. What I'd like to know is if it's possible to validate against multiple rules at once.
Typically, I've come to understand that Joomla's JForm accepts only one rule for validation, defined in the XML of the form:
Joomla's JForm internals also seem to suggest I can't, the following area being the only one I can find handing validation:
// Get the field validation rule.
if ($type = (string) $element['validate'])
{
// Load the JFormRule object for the field.
$rule = $this->loadRuleType($type);
// If the object could not be loaded return an error message.
if ($rule === false)
{
throw new UnexpectedValueException(sprintf('%s::validateField() rule `%s` missing.', get_class($this), $type));
}
// Run the field validation rule test.
$valid = $rule->test($element, $value, $group, $input, $this);
// Check for an error in the validation test.
if ($valid instanceof Exception)
{
return $valid;
}
}
This isn't wrapped in a loop, so I'm quite concerned that I can't apply multiple rules at once to a particular field.
Are you looking for server or client side validation? Sean's answer seems to cover server side so I figured I'd add some insight into client side techniques.
You enable client side validation two ways. The first and simplest would be by adding the following to your form field definition, which would ensure any required fields are filled out to proceed.
required="true"
Second would be to add a class to the form field definition to let Joomla core know you want to validate the field and how. Joomla offers 4 validations built into the core: validate-username, validate-password, validate-numeric and validate-email.
These in and of themselves don't help you much, but the ability to create and reference custom client-side validations does. For my example we're going to ensure a check box is marked before allowing the form to submit. So in the form field definition I'll add:
class="validate-checked"
On the page where you render the form, be sure to load the JS library for validation using:
JHtml::_('behavior.formvalidation');
In addition, add the class form-validate to your form HTML element.
Add this javascript to handle the actual validation, here I have a checkbox input type with an ID of tos I'm verifying. In the setHandler method, the first parameter is the custom name I entered in the form field definition class statement, validate-checked:
<script>
window.addEvent('domready', function(){
document.formvalidator.setHandler('checked', function(value) {
var tos = document.getElementById('tos');
if (tos.checked) {
return true;
} else {
return false;
}
});
});
</script>
Now, capture the submit event and verify all core and custom validations passed before submitting the form.
Joomla.submitbutton = function(task) {
if (task == 'user.cancel' || document.formvalidator.isValid(document.id(".myFormId"))) {
Joomla.submitform(task, document.getElementById('myformId'));
}
You can create as many custom client-side validation scripts as you want. Inside the setHandler method you can interact with the DOM and use the passed in value parameter to determine if the field should pass, only needing to worry about returning true or false to indicate results and Joomla will handle the rest. So you can either create one complicated validation or many smaller concise validations to suit your needs.
Hope that helps...
This is a common request. There are a few possibilities. You could write your own JFormRule with more complex validation. The other is that you could programatically add an attribute to the field that runs the additional validation sort of like what Sean is advocating.
This answer assumes that it is not possible to natively add multiple rules on one field.
Assuming that it is not possible to apply multiple rules to one field natively, then it may be possible to extend JForm::validateField() to enable such a feature by simply calling the validate method for each validation rule found.
// Extending class JForm
protected function validateField(SimpleXMLElement $element, $group = null, $value = null, JRegistry $input = null) {
if($type = (string) $element['validate'])
{
$multiple_types = explode('|', $type);
if(is_array($multiple_types) && $multiple_types[0] !== $type)
{
foreach($multiple_types as $single_type)
{
$result = parent::validateField($element, $group, $value, $input);
// Validation failed, return the result and stop validating.
if($result !== true)
{
return $result;
}
}
return true;
}
else
{
return parent::validateField($element, $group, $value, $input);
}
}
}
With that example, validation rules could be structured like:
validate="rule1|rule2"
I have a Zend_Form whose sub-form is only required in certain circumstances. The parent form and the sub-form both have required fields. The sub-form will not always be filled but when any of its elements are filled, they should all be filled.
<?php
class Cred extends Zend_Form
{
public function init()
{
$title = new Zend_Form_Element_Text('Title');
$title->setLabel('Title')
->setRequired(TRUE);
$this->addElement($title);
$award = new Zend_Form_Element_Text('Awarded');
$award->setLabel('Awarded On')
->setRequired(TRUE)
->addValidator('date');
$this->addElement($award);
$subform = new Zend_Form_SubForm();
$proof = new Zend_Form_Element_File('Documentation');
$proof->setLabel('Documentation')
->setRequired(TRUE)
->addValidator('Size', false, 409600) // limit to 400K
->addValidator('Extension', false, 'pdf');
$subform->addElement($proof);
$lang = new Zend_Form_Element_Select('Language');
$lang->setLabel('Language')->setRequired(TRUE);
$subform->addElement($lang);
$this->addSubForm($subform,'importForm');
$submit = new Zend_Form_Element_Submit('submitForm');
$submit->setLabel('Save');
$this->addElement($submit);
$this->setAction('/cred/save')
->setMethod('post')
->setEnctype(Zend_Form::ENCTYPE_MULTIPART);
}
}
When I call $form->isValid($_POST), it validates both the parent form and the sub-form and returns errors when the subform's required elements are empty even when the sub-form itself is not required.
Other than overloading the isValid() function, is there any way to validate only the parent form?
If you look into the source code the isValid() method of Zend_Form you see that there is no explicit mechanism that prevents the execution of the validators on the subforms (line 2273 ff).
Anyway, If I understand your requirement "The sub-form will not always be filled but when any of its elements are filled, they should all be filled." correctly then I think your problem does not necessarily have something to do with subforms per se but rather with conditional validation. This can be solved pretty easy with a custom validator: How to validate either Zend_Form_Element_File or Zend_Form_Element_Text are not empty.
Just keep in mind that the elements in $context contain only the subform elements.
I need help understanding the logic to deal with validating user inputs.
my current state of validating user data is at worst, i feel pretty awkward using these messy lines of codes, have a look at my typical function which i uses it to get input from the user and process it to database.
public function saveUser($arguments = array()) {
//Check if $arguments have all the required values
if($this->isRequired(array('name','email','password','pPhone','gender','roleId'))) {
//$name should could minimum of 5 and maximum of 25 chars, and is a strict character.
$name = $this->isString(5, 25, $this->data['name'], 'STRICT_CHAR');
$email = $this->isEmail($this->data['email']);
$pPhone = $this->isString(5, 12, $this->data['pPhone'], 'STRICT_NUMBER');
$sPhone = (!empty($this->data['sPhone'])) ? $this->isString(5, 12, $this->data['sPhone'], 'STRICT_NUMBER') : 0;
//Check For Duplicate Email Value
$this->duplicate('user_details','email',$email);
//If Static Variable $error is not empty return false
if(!empty(Validation::$error)) { return false; }
//After Validation Insert the value into the database.
$sth = $this->dbh->prepare('INSERT QUERY');
$sth->execute();
}
}
Now is the time i focus on improving my validation code. i would like all my class methods to validate the user inputs before inserting into the database. basically a class methods which takes user input should be able to perform the following.
If class method accepts user input as an array then check for all required inputs from within the array.
Check the Minimum and Maximum Length of the input.
Check for any illegal character.
etc.
I would like to know how to deal with this, and also if there is any PHP Independent Validation Component which can come to my rescue. it would be of great help. if i am doing it wrong please suggest me on improving my code, i won't mind going to any extent as long as it guarantees that my code follows the coding standard.
I will also appreciate if someone could explain me on dealing with validation logic for validating user input for a class method of an object.
Thank you..
PHP 5.2 has a new core extension called "filter functions". You can use this extension to sanitize and validate user data.
For example, to validate an email address:
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo "This (email) email address is considered valid.";
}
As for dealing with validation in general, you want to decouple the validation process from the incoming data and the objects themselves. I use the Lithium PHP framework, and their data validation class is implemented as a nearly independant utility class. Check it out for ideas on how to roll your own: http://li3.me/docs/lithium/util/Validator
Using their class, you get something like this:
// Input data. This can be an $object->data() or $_POST or whatever
$data = array(
'email' => 'someinvalidemailaddress';
);
// Validation rules
$rules = array(
'email' => array(
array('notEmpty', 'message' => 'email is empty'),
array('email', 'message' => 'email is not valid')
)
);
// Perform validation
Validator::check($data, $rules);
// If this were in your object
public validate($data = array(), $rules = array()) {
$data = !empty($data) ? $data : $this->data; // Use whatever data is available
$rules = $this->rules + $rules; // Merge $this's own rules with any passed rules
return Validator::check($data, $rules));
}
// You can have a save method like
public save() {
if ($this->validates()) {
// insert or update
}
}
// And your object would
$user = new User();
$user->data = array('email' => 'whatever');
$user->save();
And there's always Zend Validate. You can look it up at http://framework.zend.com/manual/en/zend.validate.set.html
Create your validation class first...Then when they submit the code. Just include the class on where every you have action set to on the form. You can create a loop to pass the POST or GET data though the instance which validates the input. Then if the input is good, return it(maybe as an array, that's what I do) and pass it to your database.
Example:
$validate = new validation_Class; //new instance of the validation class
$output = foreach($_POST as $input) // loop each input data into the class
{
$validate->$input;
}
Now if your validation class is setup right, you can have all the clean data stored in $output
I have an element. I want to add a custom validator and custom filter to it. The validator makes sure the input is one of several permitted values, then the filter adds some custom values to the input. This means I have to validate the original input first before running the filter. I do it in this order
$element = new Zend_Form_Element_Text('element');
$element->addValidator('PermittedValue', false);
$element->addFilter('TotalHyphen', false);
$this->addElement($element);
but this order isn't being respected. The filter runs first and changes the data, then the validator runs on the filtered data which means it always fails even for valid input. It seems from documentation that this is intentional
Note: Validation Operates On Filtered
Values Zend_Form_Element::isValid()
filters values through the provided
filter chain prior to validation. See
the Filters section for more
information.
How can I specify the order in which validators and filters run?
Sure seems like creating a custom element that supports post-validation filtering would be the way to go. How about this:
/**
* An element that supports post-validation filtering
*/
class My_Form_Element_PostValidateFilterable extends Zend_Form_Element_Text
{
protected $_postValidateFilters = array();
public function setPostValidateFilters(array $filters)
{
$this->_postValidateFilters = $filters;
return $this;
}
public function getPostValidateFilters()
{
return $this->_postValidateFilters;
}
public function isValid($value, $context = null)
{
$isValid = parent::isValid($value, $context);
if ($isValid){
foreach ($this->getPostValidateFilters() as $filter){
$value = $filter->filter($value);
}
$this->setValue($value);
}
return $isValid;
}
}
Usage would be something like this:
$elt = $form->addElement('PostValidateFilterable', 'myElement', array(
'label' => 'MyLabel',
'filters' => array(
'StringTrim',
// etc
),
'validators' => array(
'NotEmpty',
// etc
),
// here comes the good stuff
'postValidateFilters' => array(
new My_Filter_RunAfterValidateOne(),
new My_Filter_RunAfterValidateTwo(),
),
));
This keeps the validation and filtering in the form - keeping the controller thin.
Not tested, just a stab in the dark. And surely you could fatten/modify the API to add/remove filters by key, etc.
Whaddya think?
Maybe don't add the filter at all. Validate the content first in the controller, and then use the filter separately:
$request = $this->getRequest();
if ($request->isPost() && $form->isValid($request->getParams())) {
$filter = new Filter_Whatever();
$val = $filter->filter($request->getParam('element'));
... //call your model or whatever
}
I've never done this, but I suppose this (or something similar) might work.
Good point ! ,
AFAIK filters should or must run before validating the input :
from ZF docs
It's often useful and/or necessary to
perform some normalization on input
prior to validation. For example, you
may want to strip out all HTML, but
run your validations on what remains
to ensure the submission is valid. Or
you may want to trim empty space
surrounding input so that a
StringLength validator will use the
correct length of the input without
counting leading or trailing
whitespace characters.
but if and only if you are in case which can't solve mingos's answer must be the help
What you want to achieve is to change default behavior of how text element is being processed. Thus, I think you could create your own element (e.g. My_Form_Element_Text) that extends Zend_Form_Element_Text and overload its isValid() method.
Specifically you could just change second line in the orginal isValid() method, from $value = $this->getValue(); into $value = $this->getUnfilteredValue();. This way your validation will be performed using unfiltered values.
I am using Zend Framework. For a particular form, there is not enough space to show the errors next to the form elements. Instead, I want to be able to display the errors above the form. I imagine I can accomplish this by passing $form->getErrorMessages() to the view but how do I disable error messages from being shown be each element?
The proposal above does not take into account that the default decorators may change. Instead of clearing the decorators and then reapplying all except the ones you do not need, it would be better to disable the decorators you don't need at form-initialization time, like:
class My_Form_Login extends Zend_Form
{
public function init()
{
$this->setMethod('post');
$username = new Zend_Form_Element_Text('auth_username');
$username->setLabel('Username')
->setRequired(true)
->addValidator('NotEmpty')
->removeDecorator('Errors')
->addErrorMessage("Please submit a username.");
etc.....
You could then, wherever you use the form, decide how to display the messages (if you're planning to display them apart from your form). Of course if they should be part of the form, just create a suitable decorator and add it to the form-element init method above. Here is a nice tutorial on form decorators from ZendCasts.com
Example of displaying messages apart from the form itself.
$elementMessages = $this->view->form->getMessages();
// if there actually are some messages
if (is_array($elementMessages))
{
foreach ($elementMessages as $element)
{
foreach ($element as $message)
{
$this->view->priorityMessenger($message, 'notice');
}
}
}
The priorityMessenger-helper used above can be found here: http://emanaton.com/code/php/zendprioritymessenger
You can add decorators to form elements using setElementDecorators. Zend_Form has a function called just after init entitled loadDefaultDecorators. In your subclass, you can override this like so:
/**
* Load the default decorators for forms.
*/
public function loadDefaultDecorators()
{
// -- wipe all
$this->clearDecorators();
// -- just add form elements
// -- this is the default
$this->setDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'dl')),
'Form'
));
// -- form element decorators
$this->setElementDecorators(array(
"ViewHelper",
array("Label"),
array("HtmlTag", array(
"tag" => "div",
"class" =>"element",
)),
));
return $this;
}
Assuming you added your elements in init, that applies those decorators to each element in the form. You'll note the absence of the "Errors" decorator in setElementDecorators. You could also try looping through the form elements and using removeDecorator to remove just the Errors decorator.