Adding a validator on a Collection

zend-form
zend-input-filter

#1

We have an application where we might have a form structure such as:

Form
`- Collection
   `- Fieldset
      |- Checkbox
      `- Textarea

The Collection has a dynamic number of items at runtime.

What we’re trying to achieve: I want to validate that at least one of the checkboxes in the collection has been selected.

I have a validator (e.g. AtLeastOneOfTheCheckboxesIsSelectedValidator) currently on the Collection element which works fine, basically it validates that at least one of the Checkbox elements in the collection have been checked. We’re building the forms dynamically from a form spec (don’t ask…) so the input filter spec looks like:

my-collection
|- my-fieldset
|  |- my-textarea
|  |  `- filters
|  |     |- name = Zend\Filter\StringTrim
|  |     `- name = Zend\Filter\StripTags
|  `- type = Zend\InputFilter\InputFilter
`- validators
   `- name = App\Validator\AtLeastOneOfTheCheckboxesIsSelectedValidator

However, looking at how createInput and createInputFilter work, it seems that the filters added to my-textarea are never added, because my-collection is not an InputFilterInterface (because I didn’t specify that as the type, so it treats it as an Input). It seems it can either behave as an InputFilterInterface (and contain Inputs) or as an Input (and have validators/filters), but not both. I only noticed this because we discovered the StringTrim and StripTags weren’t actually running.

The AtLeastOneOfTheCheckboxesIsSelectedValidator gets passed the array of values inside the collection, which means we have access to all the values; the validator loops over the values, identifies which element is the checkbox and when it finds a checked one returns true early. If it can’t find a checked one, we add a message and return false (no checkboxes selected).

The problem, in short the AtLeastOneOfTheCheckboxesIsSelectedValidator on the collection works fine, but the filters for any elements below the collection are not added due to the way the Zend\InputFilter\Factory works. I can modify the spec so that the filters are added properly, but then the AtLeastOneOfTheCheckboxesIsSelectedValidator does not get added.

Any advice, or ideas on how to make this work, would be appreciated.

We’re using Zend\Form 2.10.0 and Zend\InputFilter 2.7.3 currently (in an Expressive 2.0.2 application on PHP 5.6, but I think that’s largely irrelevant).


#3

I think you need to use CollectionInputFilter.
Create a input filter for your “my-fieldset” which will include AtLeastOneOfTheCheckboxesIsSelectedValidator and input filter for “my-collection” in which you add input filter for “my-fieldset” like this:

public function init()
{
    $this->add([
            'type' => CollectionInputFilter::class,
            'input_filter' => [
                'type' => your_my-fieldset_input_filter
            ],
        ], name_of_the_collection);
}

Now I suck at explaining things but it should help you get the basic idea of what you need to do.
In one of my projects I have collections that can have other collections or even different fieldsets.
Also note that, depending on how you build it, your input filter might return bunch of data with null values like in my case (because collections that have collections that can have different fieldsets…).


#4

Hmm, not sure how this would help. See:

On L181 if the $input instanceof InputFilterInterface (which CollectionInputFilter is), it just returns, so it’ll never get down to the foreach loop on L199 where it will add from the filters and validators keys; therefore adding the AtLeastOneOfTheCheckboxesIsSelectedValidator would get ignored. Similarly, if we treat the Collection as an Input (as we have it currently set up), any filters/validators further down get ignored anyway. I believe this holds true at any level (therefore, putting the validator in the Collection or Fieldset shouldn’t make a difference).

I may’ve missed the point here though of course…

Right now the only way I can see this working is something hacky like adding an element that is never used (a type="hidden perhaps) as a sibling of the collection and adding the validator on that element, as then the validator would be able to access the correct context and whatnot…


#5

Right now the only way I can see this working is something hacky like adding an element that is never used (a type="hidden perhaps) as a sibling of the collection and adding the validator on that element…

OK, I understood that part :slightly_smiling_face: because I have been there and reluctantly done that, but I am still struggling with validating elements within fieldsets with a Collection, especially where the validation rules are contingent on the state of other things.

If anyone were to publish a nice clear example, I’d be grateful. And if I figure it out before that happens, I hereby promise to share with the world.


#6

@davidmintz the key is to remember that a validator will always get the context at the level of the element and below, and never anything “above” it as such in the tree. Therefore, a sibling “shadow” element works just fine at the same level as the collection. Might not be the nicest solution, but it works at least :slight_smile: