Form Fieldsets do not allow error messages to be set. #5265

Closed
austinsmorris opened this Issue Oct 14, 2013 · 8 comments

Comments

Projects
None yet
4 participants
Contributor

austinsmorris commented Oct 14, 2013

It is perfectly acceptable to perform validation on a Fieldset. The problem is that you can't set a message for that fieldset if validation fails. This is because the Fieldset's setMessages() and getMessages() methods only look for their elements' messages. But a Fieldset is an Element, so why can't it have it's own messages?

My use case is when checking that a db row with a composite primary key already exists. I have a Fieldset with three inputs to represent this row (it is a three way composite key). I wrote a custom validator against the Fieldset to make sure the combination of three inputs does not already exist in the db. But, I can't show an error message on the form because error messages never get set against the Fieldset.

Contributor

steverhoades commented Oct 16, 2013

@austinsmorris can you provide a sample test case for this?

Contributor

austinsmorris commented Oct 18, 2013

@steverhoades, My co-worker, @mrkrstphr, and I are running into a even bigger issue. It seems impossible to apply validators to both a fieldset and elements within that fieldset. At least this is true using getInputFilterSpecification(). We are working on better understanding the issue so that we can accurately describe it, provide test cases, and suggest a solution. At that point we will either add to this issue, or create a new one.

In the mean time, do you know of any way to apply a validator to a fieldset (in order to validate a combination of inputs) and elements within that fieldset (e.g. not empty)?

Contributor

steverhoades commented Oct 18, 2013

@austinsmorris If you can provide a gist of how you are using it now, perhaps I can write a test case to cover it?

Contributor

austinsmorris commented Oct 18, 2013

Steve, please see https://gist.github.com/austinsmorris/7048425.

As I see it, there are two problems. The first, which this issue was created to address, is that error messages from validation of the FooFieldset (as declared in FooForm) cannot be displayed because 'foo' is not an element in FooFieldset. On the contrary, messages from the validation of the 'code' select element (as declared in FooFieldset) do get displayed.

This happens because of this line:
https://github.com/zendframework/zf2/blob/master/library/Zend/Form/Fieldset.php#L306

and this line:
https://github.com/zendframework/zf2/blob/master/library/Zend/Form/Fieldset.php#L331

Messages never get set on the Fieldset because the name of the Fieldset ('foo), is not the name of an element in that Fieldset. I have managed to work around this issue by overriding the setMessages() and getMessages() method in my Fieldset:
https://gist.github.com/austinsmorris/7048564

Please let me know if you have any questions about this. These gists are a simplified version of what I am actually using.

Contributor

austinsmorris commented Oct 18, 2013

The other issue, which I see as even more important than this one, is that if validation is applied to a Fieldset, validation is no longer applied to elements in that Fieldset.

Using the gists from the comment above as an example, I apply validation to the FooFieldset (as declared in FooForm). This prevents validation (including 'required' => true) from being applied to the 'code' select element (as declared in FooFieldset).

This happens because of this line:
https://github.com/zendframework/zf2/blob/master/library/Zend/Form/Form.php#L810

If FooForm implements InputFilterProviderInterface, an InputFilter is created for the FooFieldset named 'foo' by calling getInputFilterSpecification() on the Form. Then, in the foreach loop that is meant to create the InputFilter for child Fieldsets and Elements, it sees that an InputFilter already exists for 'foo' and fails to create an InputFilter for and of the Elements within 'foo'.

Please let me know if you'd like me to create a separate issue for this problem. Also, please let me know if you have any questions.

Blaimi commented Sep 19, 2014

class FooForm extends Form implements InputFilterProviderInterface {

    public function __construct()
    {
        parent::__construct();

        $barFieldset = new BarFieldset();

        $this->add(
            array(
                'type'    => 'Zend\Form\Element\Collection',
                'name'    => 'bar',
                'options' => array(
                    'count'                  => 0,
                    'should_create_template' => true,
                    'allow_add'              => true,
                    'allow_remove'           => true,
                    'target_element'         => $barFieldset,
                )
            )
        );

    }

    public function getInputFilterSpecification()
    {
        return array(
            'bars' => array(
                'validators' => array(
                    array(
                        'name' => 'NotEmpty',
                        'options' => array(
                            'type' => NotEmpty::EMPTY_ARRAY,
                        ),
                    ),
                    array(
                        'name' => 'NotEmpty',
                        'options' => array(
                            'type' => NotEmpty::NULL,
                        ),
                    ),
                ),
                'required' => true,
            ),
        );
    }
}

class BarFieldset extends Fieldset implements InputFilterProviderInterface
{
    public function getInputFilterSpecification()
    {
        return array();
    }
}

My expectation now is that the collection contains the error-message "Value is required and can't be empty" if $data doesn't contain a value for 'bars' or it is an empty array.

Blaimi commented Sep 19, 2014

diff --git a/library/Zend/Form/Fieldset.php b/library/Zend/Form/Fieldset.php
index 258d5d3..3321032 100644
--- a/library/Zend/Form/Fieldset.php
+++ b/library/Zend/Form/Fieldset.php
@@ -300,6 +300,7 @@

         foreach ($messages as $key => $messageSet) {
             if (!$this->has($key)) {
+                $this->messages[$key] = $messageSet;
                 continue;
             }
             $element = $this->get($key);
@@ -323,7 +324,7 @@
     public function getMessages($elementName = null)
     {
         if (null === $elementName) {
-            $messages = array();
+            $messages = $this->messages;
             foreach ($this->byName as $name => $element) {
                 $messageSet = $element->getMessages();
                 if (!is_array($messageSet)

@mbaumgartl mbaumgartl referenced this issue in zendframework/zend-form Dec 15, 2015

Closed

Collection validation messages #26

@GeeH GeeH added the To Be Closed label Mar 5, 2016

GeeH commented Jun 27, 2016

This issue has been closed as part of the bug migration program as outlined here - http://framework.zend.com/blog/2016-04-11-issue-closures.html

@GeeH GeeH closed this Jun 27, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment