zend-framework2zend-formzend-form-select

How to fix 'The input was not found in the haystack' in ZF2?


I have an issue in my code which I can't resolve. I'm using Zend framework 2.4 and I wrote a form but as soon as I validate it, I got the error The input was not found in the haystack. This are the 3 input where I got the error:

 $this->add(array(
        'name' => 'ACTIVITE1',
        'type' => 'Zend\Form\Element\Select',
        'required' => 'required',
        'options' => array(
            'value_options' => array(
                'Choisir l\'activité'
            ),
            'disable_inarray_validator' => false,
        ),
        'attributes' => array(
            'class' => 'form-control',
            'id' => 'select-session1'
        )
    ));

    $this->add(array(
        'name' => 'ACTIVITE2',
        'type' => 'Zend\Form\Element\Select',
        'required' => 'required',
        'options' => array(
            'value_options' => array(
                'Choisir l\'activité'
            )
        ),
        'disable_inarray_validator' => false,
        'attributes' => array(
            'class' => 'form-control',
            'id' => 'select-session2'
        )
    ));

    $this->add(array(
        'name' => 'ACTIVITE3',
        'type' => 'Zend\Form\Element\Select',
        'required' => 'required',
        'options' => array(
            'value_options' => array(
                'Choisir l\'activité'
            )
        ),
        'disable_inarray_validator' => false,
        'attributes' => array(
            'class' => 'form-control',
            'id' => 'select-session3'
        )
    ));

I saw in other form that I should put 'disable_inarray_validator' => false but this doesn't work too.


Solution

  • Of course it's not working.

    That message comes from the \Zend\Validator\InArray and essentially, it means: "your user is doing something hacky with your select, pay attention".

    An exemple would be a Select Preferred fruit with two options, like "Banana" and "Ananas", but the user "hacks" the select and sends to the server the value "Audi". The InArray validator is really important and shouldn't be disabled (well, only in a few exceptions..).

    Now, why are you getting this error? The answer is... You didn't told what are the select options. You created a Select, but you didn't specified what are its options. You put the label/placeholder at the place of the options. A correct Select would be:

    $this->add(array(
        'name' => 'ACTIVITE1',
        'type' => 'Zend\Form\Element\Select',
        'required' => 'required',
        'options' => array(
            'value_options' => array(
                1 => 'Fitness',
                2 => 'Parcour',
                3 => 'Velo',
                4 => 'Tapis roulant',
                // ... and so on
            )
        ),
        'attributes' => array(
            'class' => 'form-control',
            'id' => 'select-session1',
            'placeholder' => "Choisir l'activité"
        )
    ));
    

    What is "weird" is the fact that you are filling something in an empty select, and my question then is: why?

    Selects are (typically) there for a predefined list of values. If you want to allow your users to fill a custom text, then you should consider to create a Text field with the autocomplete option.

    Edit: select with values from DB

    If you want to create a select with a list of options that come from the database, the route is a bit more complex, but once you've learned how to do it, it will become way easier.

    PAY ATTENTION: this will not be a "copy&paste solution". Since I'm not having access to your code, I'm making up names (classes, namespaces, methods, variables) just to create a complete example :)

    First off, you must create a custom element. In this case, it will be a custom select:

    namespace Yournamespace;
    
    use Zend\Form\Element\Select;
    use Yournamespace\ActivityMapper;
    
    class ActivitySelect extends Select {
    
        protected $activityMapper;
    
        public function __construct(ActivityMapper $activityMapper, $name = null, $options = []) {
            parent::__construct($name, $options);
            $this->activityMapper = $activityMapper;
        }
    
        public function init() {
            $valueOptions = [];
            foreach ($this->activityMapper->fetchAll() as $activity) {
                $valueOptions[$activity->getActivityId()] = $activity->getActivityName();
            }
            $this->setValueOptions($valueOptions);
        }
    
    }
    

    What is really important here is that you must instantiate your element (options, classes, and so on..) inside init method.

    Since this element has a dependency (ActivityMapper), you'll have to create a factory for this element:

    namespace Yournamespace;
    
    use Zend\ServiceManager\Factory\FactoryInterface;
    use \Interop\Container\ContainerInterface;
    
    class ActivitySelectFactory implements FactoryInterface {
    
        public function __invoke(ContainerInterface $container, $requestedName, array $options = null): object {
            $activityMapper = $container->get(\Yournamespace\ActivityMapper::class);
            return \Yournamespace\ActivitySelect($activityMapper);
        }
    
    }
    

    This factory must be added in the configuration, more precisely, inside module.config.php:

    return [
        // ...
        'form_elements' => [
            'factories' => [
                \Yournamespace\ActivitySelect::class => \Yournamespace\ActivitySelectFactory::class,
            ]
        ]
        // ...
    ];
    

    Now, you must modify your form too. All elements must be added to form inside init method, and not inside the constructor:

    namespace Yournamespace;
    
    use Zend\Form\Form;
    use Zend\InputFilter\InputFilterProviderInterface;
    
    class ActivityForm extends Form implements InputFilterProviderInterface {
    
        public function init() {
            parent::init();
    
            // ...
            $this->add([
                'name' => 'ACTIVITE1',
                'type' => \Yournamespace\ActivitySelect::class,
                'attributes' => [
                    'class' => 'form-control',
                    'id' => 'select-session1',
                    'required' => true, // This will work only clientside, don't forget the inputfilter!
                    'placeholder' => 'Choisir l\'activité',
                ],
                'options' => [
                    'label' => 'Choisir l\'activité',
                ]
            ]);
            // ...
    
        }
    
        public function getInputFilterSpecification() {
            // ...
            $inputFilter[] = [
                'name' => 'ACTIVITE1',
                'required' => true
            ];
            // ...
            return $inputFilter;
        }
    
    }
    

    Finally, you'll have to modify your controller too, because you need to retrieve the form from the FormElementManager:

    namespace Yournamespace;
    
    use Zend\Form\FormElementManager;
    
    class YourController extends AbstractActionController {
        private $formManager;
    
        public function __construct(FormElementManager $formManager) {
            $this->formManager = $formManager;
        }
    
        public function choisirActiviteAction(){
            // ...
            $form = $this->formManager->get(\Yournamespace\ActivityForm::class);
            // ...
        }
    }
    

    A next nice step would be to create a controller plugin for the $formManager, instead of making it as a dependency of each controller, but this is a different problem..