Issues

ZF-5788: The documentation on the use of Zend_Form_Element::setRequired(), ::setAllowEmpty() and the 'NotEmpty' Validator is fuzzy and misleading

Description

http://framework.zend.com/manual/en/…

The section explaining the use of Zend_Form_Element::setRequired(), ::setAllowEmpty() and the 'NotEmpty' validator lacks clarity. Asking for an explanation about it in IRC just confirmed to me that this particular process is misunderstood. I had to dig into the code itself to understand what's actually going on. This is what the manual says:

{quote} In addition to validators, you can specify that an element is required, using setRequired(true). By default, this flag is false, meaning that your validator chain will be skipped if no value is passed to isValid(). {quote}

1-) My first issue with this section is that setRequired() should not be introduced here. Much more clarity would be achieved by first discussing setAllowEmpty() and the 'NotEmpty' Validation rule, because setRequired() is a redundant feature. The same effect can be obtained by explicitly setting the 'allowEmpty' flag to false and explicitly adding the 'NotEmpty' validation to the chain. setRequired() is nothing more than a convenience method, a shortcut. It only has influence when the flag is set to true, in which case it does 2 things: - it ignores the 'allowEmpty' flag (acting as if allowEmpty had been set to false). Therefore it is important to understand what the allowEmpty flag does to understand how setRequire() influences the behavior of the form. - it adds a 'NotEmpty' validator to the validation chain, (provided that the Zend_Form_Element::$autoInserNotEmptyValidator flag is set to true). As for the allowEmpty flag it would be better to understand what the NotEmpty validation rule does. AFAICT, it has no other purpose.

2) Unlike stated in that little paragraph, setRequired(false) does not imply that the validators chain will be skipped when an empty value is passed to isValid(). That behavior is controlled by the 'allowEmpty' flag, which remains unaffected when the 'required' flag is set to false. setRequired(false) simply deactivate the shortcut mentioned earlier in point 1. When the 'required' flag is set to false, there is no more bypassing the allowEmpty flag and no implicit addition of the 'NotEmpty' validator to the chain.

{quote} By default, when an element is required, a flag, 'allowEmpty', is also true. This means that if a value evaluating to empty is passed to isValid(), the validators will be skipped. You can toggle this flag using the accessor setAllowEmpty($flag); when the flag is false, then if a value is passed, the validators will still run. {quote}

3) Again, the wording is misleading. setRequired(true), sets the 'required' flag to true, but has no direct influence on the 'allowEmpty' flag itself. It doesn't set allowEmpty to false, it simply acts as if allowEmpty was false (see point 1 and 2). Instead the paragraph should have explained how allowEmpty influences validation behavior and how setting 'required' to true changes that behavior.

setAllowEmpty(true) allows an element to skip validation when populated with an empty value. Setting the 'required' flag to true on an element will ignore that property of the allowEmpty flag, without changing the flag itself. The allowEmpty flag is useful for non mandatory fields (empty value acceptable), that still need to be checked for proper format when a value is provided. The flag is not part of the validation chain, but modifies the validation behavior for the element.

Comments

edited for clarity

In addition to validators, you can specify that an element is required, using {{setRequired($flag)}}. By default, this flag is {{FALSE}}. In combination with {{setAllowEmpty($flag)}} ({{TRUE}} by default) and {{setAutoInsertNotEmptyValidator($flag)}} ({{TRUE}} by default), the behavior of your validator chain can be modified in a number of ways:

  • Using the default Flags, validating an Element without passing a value, or passing an empty string for it, skips all validators and validates to {{TRUE}}.

  • {{setAllowEmpty(false)}} leaving the two other mentioned flags untouched, will validate against the validator chain you defined for this Element, regardless of the value passed to {{isValid()}}.

  • {{setRequired(true)}} leaving the two other mentioned flags untouched, will add a 'NotEmpty' validator on top of the validator chain (if it was not already added), with the {{$breakChainOnFailure}} flag set. This behavior lends required flag semantic meaning: if no value is passed, we immediately invalidate the submission and notify the user, and prevent other validators from running on what we already know is invalid data. \ \ If you do not want this behavior, you can turn it off by passing a {{FALSE}} value to {{setAutoInsertNotEmptyValidator($flag)}}; this will prevent {{isValid()}} from placing the 'NotEmpty' validator in the validator chain.


Index: documentation/manual/en/module_specs/Zend_Form-Elements.xml
===================================================================
--- documentation/manual/en/module_specs/Zend_Form-Elements.xml (Revision 21783)
+++ documentation/manual/en/module_specs/Zend_Form-Elements.xml (Arbeitskopie)
@@ -619,30 +619,37 @@
 
         
             In addition to validators, you can specify that an element is
-            required, using setRequired(true). By default, this
-            flag is FALSE, meaning that your validator chain will be skipped if
-            no value is passed to isValid(). You can modify this
-            behavior in a number of ways:
+            required, using setRequired($flag). By default, this
+            flag is FALSE. In combination with 
+            setAllowEmpty($flag) (TRUE
+            by default) and setAutoInsertNotEmptyValidator($flag)
+            (TRUE by default), the behavior of your validator chain
+            can be modified in a number of ways:
         
-                    By default, when an element is required, a flag,
-                    'allowEmpty', is also TRUE. This means that if a value
-                    evaluating to empty is passed to isValid(), the
-                    validators will be skipped. You can toggle this flag using
-                    the accessor setAllowEmpty($flag); when the
-                    flag is FALSE and a value is passed, the validators
-                    will still run.
+                    Using the defaults, validating an Element without passing a value, or
+                    passing an empty string for it, skips all validators and validates to
+                    TRUE.
                 
-                    By default, if an element is required but does not contain
-                    a 'NotEmpty' validator, isValid() will add one
-                    to the top of the stack, with the
+                    setAllowEmpty(false) leaving the two other
+                    mentioned flags untouched, will validate against the validator chain
+                    you defined for this Element, regardless of the value passed
+                    to isValid().
+                
+            
+
+            
+                
+                    setRequired(true) leaving the two other
+                    mentioned flags untouched, will add a 'NotEmpty' validator
+                    on top of the validator chain (if none was already set)), with the
                     $breakChainOnFailure flag set. This behavior lends
                     required flag semantic meaning: if no value is passed,
                     we immediately invalidate the submission and notify the

Reading in the sources, this is somewhat mixing responsibilities, what about throwing setAutoInsertNotEmptyValidator() away, in the favour of just using setRequired() in combination with setAllowEmpty() ?


    public function isValid($value, $context = null)
    {
        $this->setValue($value);
        $value = $this->getValue();

        if ((('' === $value) || (null === $value))
            && !$this->isRequired()
            && $this->getAllowEmpty()
        ) {
            return true;
        }

        if ($this->isRequired()
            && !$this->getAllowEmpty() // && $this->autoInsertNotEmptyValidator()
            && !$this->getValidator('NotEmpty'))
        {
            $validators = $this->getValidators();
            $notEmpty   = array('validator' => 'NotEmpty', 'breakChainOnFailure' => true);
            array_unshift($validators, $notEmpty);
            $this->setValidators($validators);
        }
...

After talking to Matthew, i finally got the concept why setAutoInsertNotEmptyValidator was introduced, with my solution in the previous comment the one call to setRequired(true) which is obviously a shortcut for convenience would be achieved by setRequired(true) && setAllowEmpty(false).

//edit What made me struggle a while is the following, as a side-effect, setAllowEmpty(false) while leaving the other flags default, will be the same as setRequire(true) and setAutoInsertNotEmptyValidator(false), which is a bit confusing because of the naming.

So just changing the Documentation is enough, everything else i can think of would remove Choices which is probably a bad thing to do.

Patch applied in trunk and 1.10 release branch