Search code examples
phpsymfonysymfony-2.8

How to make sure that a Symfony form does not contain unnecessary elements?


I am a beginner in Symfony, so this question might be simple for those who are more experienced in this framework.

I am building forms and I have several possible types of items in forms. At this moment these are:

  • text
  • html
  • image

However, in the future there will be many more items. Previously, the project generated all items into their place (text, html and image) and hidden those which are not needed for the specific form item (maximum one is needed). However, I intend to avoid adding items I do not need. Since I do not know at the point buildForm of an arbitrary item is running whether it is text, html or image, so at this point all of them are added (I know this is counter-intuitive, but this is a code I try to refactor):

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->add('layoutTypeInput', TextType::class);

    $builder->add('blockTypeOutput', EntityType::class, array(
        'class' => 'MyPageBundle:BlockTypeOutput',
        'choice_label' => 'titleHu',
        'required' => false,
        'placeholder' => 'Válassz blokk kimenetet!',
        'empty_data' => null,
    ));

    $builder->add('text', TextType::class, $this->getBlockTypeOptions('text'));

    $builder->add('html', TextareaType::class, $this->getBlockTypeOptions('html'));

    $builder->add('image', ImageSelectType::class, $this->getBlockTypeOptions('image'));

    $builder->addEventListener(FormEvents::POST_SET_DATA, array($this, 'onPostSetData'));
    $builder->addEventListener(FormEvents::PRE_SUBMIT, array($this, 'onPreSubmit'));
}

Now, I have a function which removes the unnecessary elements of a form:

private function removeUnnecessaryItems(\Symfony\Component\Form\FormInterface $form, $key)
{
    $keys = ['text', 'html', 'image'];
    foreach ($keys as $k) {
        if ($key !== $k) $form->remove($k);
    }
}

And inside onPostSetData I call it like this:

    $this->removeUnnecessaryItems($form, $inputObject->getLayoutTypeIdText());

and finally, in the twig I determine what should be generated into the form:

            {% for ioLayoutBlock in form.ioLayoutBlocks %}
                <div class="row">
                    <div class="col-xs-12 col-md-3">
                        {{ form_errors(ioLayoutBlock.layoutTypeInput) }}
                        {{ioLayoutBlock.layoutTypeInput.vars.label}}
                    </div>
                    {{ form_widget(ioLayoutBlock.layoutTypeInput, {'attr' : {'class':'hidden'}}) }}
                    <div class="col-xs-12 col-sm-6 col-md-5">
                        {{ form_errors(ioLayoutBlock.blockTypeOutput) }}
                        {{ form_widget(ioLayoutBlock.blockTypeOutput, {'attr' : {'class':'blockTypeOutput'}}) }}
                    </div>
                    <div class="col-xs-12 col-sm-6 col-md-4">
                        {% if ioLayoutBlock.text is defined %}
                            {{ form_errors(ioLayoutBlock.text) }}
                            {{ form_widget(ioLayoutBlock.text, {'attr':{'class':'hidden uniqueInput ioLayoutBlock_text' }}) }}
                        {% elseif ioLayoutBlock.html is defined %}
                            {{ form_errors(ioLayoutBlock.html) }}
                            {% if layout.layoutType.name == 'userHTML' %}
                                <div class="input-group ioLayoutBlock_html hidden">
                                    <a class="input-group-addon myAdminForm" target="_blank" data-my-href="page/{{ page.id }}/wysiwyg/{{ layout.id }}"><span class="glyphicon glyphicon-pencil"></span></a>
                                    {{ form_widget(ioLayoutBlock.html, {'attr':{'class':'uniqueInput wysiwyg' }}) }}
                                </div>
                            {% else %}
                                {{ form_widget(ioLayoutBlock.html, {'attr':{'class':'hidden uniqueInput wysiwyg ioLayoutBlock_html' }}) }}
                            {% endif %}
                        {% elseif ioLayoutBlock.image is defined %}
                            {{ form_errors(ioLayoutBlock.image) }}
                            {{ form_widget(ioLayoutBlock.image, {'attr':{'class':'hidden uniqueInput ioLayoutBlock_image' }}) }}
                        {% endif %}
                    </div>
                </div>
            {% endfor %}

and if I load the page, everything is shown correctly, but unfortunately, when I try to submit the form, it gives the error of

This form should not contain extra fields.

as many times as many form items I have. If I comment out the call on removeUnnecessaryItems inside onPostSetData and subsequently remove the conditionals from the twig, like:

{% if ioLayoutBlock.text is defined %}

then everything works, but that's how it worked before the refactor. Ideally I would like to avoid adding so many unnecessary things at buildForm, but I do not know how can I load up any meaningful data there to determine the type of the item. Alternatively I would like to ensure that the items are successfully removed at the events where I do know their type, without the form errors on submit I described above. So, my question is: How can I avoid generating all kinds of unnecessary stuff into my form without being blocked by submit errors?


Solution

  • My approach would be to give a parameter to your form

    $form = $this->createForm(DynamicType::class, $user, [
        'layout_type_id' => $layoutTypeIdText,
    ]),
    

    Then adds the fields depending of the param

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $this->layout_type = $options['LayoutTypeId'];
        // [...]
        if ($this->layout_type !== 'text' )
            $builder->add('text', TextType::class, $this->getBlockTypeOptions('text'));
            // [...]
        ;
    }
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            // [...]
            'layout_type_id' => null,
        ]);
    }
    

    With this approch, the good part is you don't to duplicate your logic in twig, the form get the fields it needs, so you can just render the form using

    {{ form_start(form) }}
    {{ form_widget(form) }}
    {{ form_end(form) }}
    

    For your specific case you need to avoid adding the items at buildForm, to handle the default empty values at onPreSubmit (as the transformers will not be called if the items are not added at buildForm) and to add the effective items at onPreSubmit.