An optional collection input filter - not passing empty/optional validation

912 views Asked by At

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/

0

There are 0 answers