Validating a Form Collection of Text fields #4482

Closed
iquabius opened this Issue May 15, 2013 · 15 comments

Projects

None yet

8 participants

@iquabius
Contributor

I tried to validate a form collection that have Text element as the target, I tried several different approaches and none of them worked, as I'm seeing there is no easy way to do it.

My fieldset looks like this:

class ProductFieldset extends Fieldset implements InputFilterProviderInterface
{
    public function init()
    {
        $this->add(array(
            'name' => 'features',
            'type' => 'Collection',
            'options' => array(
                'label'          => 'Product features',
                'count'          => 0,
                'should_create_template' => true,
                'allow_add'      => true,
                'target_element' => array(
                    'type'    => 'Text',
                ),
            ),
        ));
    }
}

I tried to validate this by implementing getInputFilterSpecification() but it did not work well. I tried to use the CollectionInputFilter and it did not work as well. Am I missing something or there isn't a way to do this with the current Zend\Filter implementation? It seems that there should be a InputCollection to do this kind of thing...

@bakura10, @davidwindell ping!

@iquabius
Contributor

After looking at #4328 it seems that the easier way to do this is by wrapping the target element (Text) in a fieldset, is that right?

@localheinz
Member

What do you mean by target element?

@iquabius
Contributor

The collection target element. In my case I need a collection of Text elements, so in this case the Text element is the target element!

@danizord
Contributor

@wryck7 I actually use a Fieldset with 1 Element\Text as target element. This is useful in your case if a Feature is an entity that needs an object, hydrator, ..

@iquabius
Contributor

My entity is actually the Product, and it has an array of Features as an attribute. The Feature by itself is not an entity, it is just a simple string.

@webdevilopers
Contributor

I thought the only attempt to do this was the one @danizord mentionend, but looking at the docs:

setTargetElement($elementOrFieldset)

This function either takes an Zend\Form\ElementInterface, Zend\Form\FieldsetInterface instance or an array to pass to the form factory.
When the Collection element will be validated, the input filter will be retrieved from this target element and be used to validate each element in the collection.

Have you found a way how to use set the input filter for a single form element, @wryck7 ?

@SteveTalbot

I've been wrestling with this too. Eventually I worked out that Zend\Form\Form::attachInputFilterDefaults() will construct default input filters for a collection of fieldsets, but it never constructs input filters for a collection of standard elements.

The reasons for this are:

  1. Zend\Form\Element\Collection does not implement InputFilterProviderInterface.
  2. Because of this, Zend\Form\Form::attachInputFilterDefaults() instantiates a new empty input filter for each collection (line 814).
  3. When Zend\Form\Form::attachInputFilterDefaults() recursively populates subfilters for the collection, it ignores a collection that contains only elements (line 763).

I haven't tested this thoroughly, but I think the problem could be fixed by a single change to line 763 of Zend\Form\Form as follows:

        if (!$fieldset instanceof Collection || $inputFilter instanceof CollectionInputFilter || !$fieldset->getTargetElement() instanceof FieldsetInterface ) {

I'd also recommend a change near line 814 as follows:

                            if( $childFieldset instanceof Collection ) {
                                $inputFilter->add(new CollectionInputFilter(), $name);
                            } else {
                                $inputFilter->add(new InputFilter(), $name);
                            }

@iquabius @webdevilopers You could implement a workaround without waiting for a change to ZF2 by extending the Form class and overriding attachInputFilterDefaults() as follows:

    public function attachInputFilterDefaults(InputFilterInterface $inputFilter, FieldsetInterface $fieldset) {

        // Our workaround only applies to collections of standard elements
        if( $fieldset instanceof Collection ) {
            $targetElement = $fieldset->getTargetElement();
            if( !$targetElement instanceof FieldsetInterface ) {

                // Create an input for each element
                $inputFactory = $this->getFormFactory()->getInputFilterFactory();
                foreach( $fieldset->getElements() as $name => $element ) {
                    if( $element instanceof InputProviderInterface ) {
                        $inputSpec = $element->getInputSpecification();
                    } else {
                        $inputSpec = array('name' => $name, 'required' => false);
                    }
                    $inputFilter->add($inputFactory->createInput($inputSpec), $name);
                }

                // Skip standard logic
                return;
            }
        }

        // Otherwise proceed as normal
        return parent::attachInputFilterDefaults($inputFilter, $fieldset);
    }

I've just implemented this in our local code so we can try it out and see if it breaks anything else...

@SteveTalbot SteveTalbot referenced this issue Mar 31, 2014
Merged

Fix 4294 #4328

@davidwindell
Contributor

@SteveTalbot can you raise a pull request for this please so we can check and see if tests pass

@davidwindell
Contributor
@DennisDobslaf

Is the a final (well documented) solution on this? Stepped on the exactlly same issue!

@adamlundrigan
Member

I was experiencing the exact same issue with ZF2 2.3.1. When I tried to reproduce against master I could not, and when I bumped the ZF2 version constraint to 2.3.*@dev the problem disappeared. I did a git bisect and a50698d is the point where it's fixed for me. @DennisDobslaf @SteveTalbot could you try the same thing and see if it works for you? If so we can safely close this issue as it'll be fixed in 2.3.2 when it's released.

@SteveTalbot

@adamlundrigan I agree, this works for my use case, and I'm happy to close #4482.

There is a related problem for collections of fieldsets, because attachInputFilterDefaults() never automatically instantiates a CollectionInputFilter for collections of fieldsets. You always have to instantiate it externally as you describe in your blog post: http://adam.lundrigan.ca/2014/05/04/standalone-input-filter-classes-and-zf2-form-collections/

I think it should be possible to automate this, but every time I tried to fix it for a single use case, I ended up introducing bugs that broke other use cases. Do you think this is worthy of opening a new issue ticket?

@DennisDobslaf

I'm on holiday right now. I will try this later. But if the solution works for @SteveTalbot, I think it will do for me too.

@SteveTalbot

I raised a new ticket #6497 for the related problem about automatically instantiating collection input filters, and pull request #6498 containing my fix.

@adamlundrigan
Member

@Ocramius this ticket can be closed. Issue was resolved by #6146

@adamlundrigan adamlundrigan added a commit to adamlundrigan/zf2 that referenced this issue Nov 15, 2014
@adamlundrigan Steve Talbot + adamlundrigan Workaround for #6263, which is related to #4482 9553be7
@adamlundrigan adamlundrigan added a commit to adamlundrigan/zf2 that referenced this issue Nov 15, 2014
@adamlundrigan Steve Talbot + adamlundrigan Workaround for #6263, which is related to #4482 31f2bc0
@Ocramius Ocramius added a commit that referenced this issue Nov 19, 2014
@Ocramius Steve Talbot + Ocramius Workaround for #6263, which is related to #4482 8e70ab5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment