po_taka po_taka - 2 months ago 22
PHP Question

Symfony form collection rendering

I'm using symfony 2.3
I have form with field of type "collection"

Visualization code in twig:

{% for field in form.fields %}
{{ form_row(field.name) }}
{% endfor %}


Everything work, expect when form.fields is empty.
Then nothing is visualized in twig loop, witch is fine.
But at the end of the form there is "label" for the element "form.fields". Only label.

Workaround:

{% for field in form.fields %}
{{ form_row(field.name) }}
{% endfor %}
<div class="hidden">
{{ form_row(form.fields) }}


If there are elements, they will be rendered in the loop.
{{ form_row }} will be empty, because all elemets are iterated in the loop above.
But if form.fields is empty then there is "hidden" (in the div) label.

What I'm missing !? Why this is happening !?

Hidden div content:

<div class="form-group"><label class="col-sm-2 control-label required">name</label><div class="col-sm-10"><div id="my-id" data-prototype=""></div></div></div>


Builder config:

$builder->add(
'fieldDataMappers',
'collection',
array(
'type' => new FieldDataType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
)
);

Answer Source

As you have correctly guessed, the Symfony TwigBridge keep track of what is rendered and what is not. This is useful, since there is a function called form_rest(form), which is especially useful for printing hidden form field, and to prevent the "great jupiter! I forgot to print that field!" moments. :) You often find form_rest at the end of the form, just before the submit button.

Also consider that the collection IS a composite form type, which contains a variable list of child form. When the for loop is not triggered, since the form type is empty, the call to {{ form_row(form.fields) }} print out the collection form type. By default, this will print (you've guessed it) the collection label and an empty div. On the other hand, when the collection is not empty, Symfony will consider the collection as rendered, since all children are already rendered (see FormView::isRendered)

You can take a look into Symfony standard theme form_div_layout.html.twig, especially the blocks form_row (which show label printing) and form_widget_compound (the div and the for loop).

So, if you just need to hide the label (quick and dirty, some div are still there), just use:

$builder->add(
    'fieldDataMappers',
    'collection',
    array(
        'type' => new FieldDataType(),
        'label' => false,
        'allow_add' => true,
        'allow_delete' => true,
        'by_reference' => false,
    )
);

Or better, simply output the whole collection widget, without row:

{{ form_widget(form.fieldDataMappers) }}

Or even better, you print the whole collection with:

{{ form_row(form.fieldDataMappers) }}

...and then add a Twig theme to customize the collection output with something like (note the name syntax, and the missing form_label call):

{% block collection_row -%}
    <div>
        {{- form_errors(form) -}}
        {{- form_widget(form) -}}
    </div>
    <div class="hidden">Something here?</div>
{%- endblock collection_row %}

Hope this help!