0

I have a fairly complex form, which is presented in a tabbed format. Either of the fieldsets in the form need to be completed, so i am looking at using an OptionalInputFilter. The fieldset i am having the issue with is a collection. I have configured the input filter to work with the collection ok, but the optional aspect does not seem to work out the box. To be clear, the desired effect is that the user can either add a collection of items, of which each element is fitered/validated, or the user leaves it blank, and it passes validation.

The fieldset in question:

class EventFieldset extends Fieldset
{
    public function __construct($name = null, $options = [])
    {
        parent::__construct('Events', $options);
    }

    public function init()
    {
        $this->add([
            'name' => 'date',
            'type'  => Date::class,
            'options' => [
            'label' => 'Date',
            'label_attributes' => [
                'class' => 'col-form-label'
            ],
        ],
        'attributes' => [
            'class' => 'form-control ',
            'required' => true,
        ],
    ]);

    $this->add([
        'name' => 'description',
        'type'  => Textarea::class,
        'options' => [
            'label' => 'Description',
            'label_attributes' => [
                'column' => 'md-2',
                'class' => 'col-form-label'
            ],
        ],
        'attributes' => [
            'class' => 'form-control',
            'minlength' => '10',
            'required' => true,
        ],
    ]);

    $this->add([
        'name' => 'hours',
        'type'  => Text::class,
        'options' => [
            'label' => 'Hours',
            'label_attributes' => [
                'column' => 'md-2',
                'class' => 'col-form-label'
            ],
           'column' => 'md-10'
        ],
        'attributes' => [
            'class' => 'has-event-duration duration-only form-control'
        ],
    ]);

}

}

The Inputfilter for the above fieldset:

class EventInputFilter extends OptionalInputFilter
{
public function init()
{
    parent::init();
    $this->add([
        'name' => 'date',
        'validators' => [
            [
                'name' => Date::class,
                'options' => [
                    'format' => 'Y-m-d',
                    'strict' => true,
                ]
            ],
        ],
        'filters' => [
            ['name' => StripTags::class],
            [
                'name' => DateTimeFormatter::class,
                'options' => [
                    'format' => 'Y-m-d'
                ]
            ]
        ]
    ]);

    $this->add([
        'name' => 'description',
        'required' => true,
        'validators' => [
            [
                'name' => StringLength::class,
                'options' => [
                    'min' => 5,
                    'max' => 25600
                ]
            ]
        ],
        'filters' => [
            ['name' => StripTags::class],
            ['name' => StringTrim::class]
        ]
    ]);

    $this->add([
        'name' => 'hours',
        'required' => true,
        'validators' => [
            ['name' => AlnumValidator::class],
        ],
        'filters' => [
            ['name' => StripTags::class],
            ['name' => Alnum::class]
        ]
    ]);

}

}

Note how it extends the OptionalInputFilter.

The form:

class MyForm extends Form
{
public function init()
{
    parent::init();
    //other elements / fieldsets removed for brevity
    $this->add([
        'name' => 'manualRecords',
        'type' => ManuallyEnteredRecordsFieldset::class,
    ]);

    $this->add([
        'type' => Csrf::class,
        'name' => 'csrf',
        'options' => [
            'csrf_options' => [
                'timeout' => 600,
            ],
        ],
    ]);

    $this->add([
        'name' => 'Submit',
        'type' => Submit::class,
        'options' => [
            'label' => 'Submit',
            'variant' => 'outline-primary'
        ],
        'attributes' => [
            'class' => 'btn btn-primary',
            'value' => 'Submit'
        ]
    ]);
}

}

class ManuallyEnteredRecordFieldset extends Fieldset
{
public function init()
{
    //other elements here - removed for brevity
    $this->add([
        'name' => 'Events',
        'type' => Collection::class,
        'options' => [
            'count' => 1,
            'allow_add' => true,
            'should_create_template' => true,
            'target_element' => [
                'type' => EventFieldset::class
            ],
        ]
    ]);

Finally, the input filter is added in the forms factory:

class MyFormFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
    $form = new MyForm();
    $form->setInputFilter($this->getInputFilter($container->get('InputFilterManager')));
    return $form;
}

protected function getInputFilter(InputFilterPluginManager $inputFilterPluginManager) : InputFilter
{
    //other input filters applied - removed for brevity
    $eventInputFilter = $inputFilterPluginManager->get(EventInputFilter::class);

    $baseInputFilter = new InputFilter();

    $collectionInputFilter = new CollectionInputFilter();
    $collectionInputFilter->setInputFilter($eventInputFilter);
    $manualRecordInputFilter = new OptionalInputFilter();   //note - also an OptionalInputFilter
    $manualRecordInputFilter->add($collectionInputFilter, 'Events');
    $baseInputFilter->add($manualRecordInputFilter, 'manualRecords');

    return $baseInputFilter;
}

}

When i submit this form with the data present, even with 1->N rows of the collection added, it is validated fine. Without any data entered (which i would have expected to still pass, being that it is built from an Optional Inputfilter), i get the following inputfilter error messages:

["manualRecords"] => array(2) {
["Events"] => array(1) {
  [0] => array(3) {
    ["date"] => array(2) {
      ["isEmpty"] => string(36) "Value is required and can't be empty"
    }
    ["description"] => array(1) {
      ["isEmpty"] => string(36) "Value is required and can't be empty"
    }
    ["hours"] => array(1) {
      ["isEmpty"] => string(36) "Value is required and can't be empty"
    }
  }
}

This is not how the behaviour should be according to: https://docs.laminas.dev/laminas-inputfilter/optional-input-filters/

4
  • 1
    You have provided 'count' => 1 so the collection class (events) will add a default element Commented Jul 15, 2020 at 22:17
  • Thank you for replying. Yes you are right, that if i change 'count' => 0 then the form will be submitted and pass all validation - but then no collection rows are being displayed by default! I would have thought that i could have initially a single row of fields displayed (with the user able to add more), with the ability for the whole fieldset to be optional? Is there anyway i can make the first collection row non-required, and all subsequent rows required? Commented Jul 16, 2020 at 11:03
  • Think of the rendering of the form as separate from the validation and you will make your life allot easier. So, consider when you first render the form (so no form submit) you build one form with the count = 1 (it's default empty element). When you POST/submit you can build a form with 0 elements in the collection and pass the posted validation to that form instead. Commented Jul 22, 2020 at 9:41
  • If for some reason you want to perform more complex logic around the collection. I would recommend implementing your own Collection class (overload the isValid() method) - As "The form must have one element" is really validation around the collection as a whole rather than each item in isolation. Commented Jul 22, 2020 at 9:43

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.