Home > Net >  Symfony CollectionType only saves last submitted new item
Symfony CollectionType only saves last submitted new item

Time:10-29

So I have problem with my CollectionType in my Custom Form Type. When I add two new forms to the Collection and properly fill them out and submit. Only the last option is being submitted. I've been banging my head for the last four hours trying to figure this out.

class CustomType extends AbstractType
{
    public function buildForm(...)
    {
        $builder->add(
            'collection',
            CollectionType::class,
            [
                'required' => false,
                'entry_type' => CustomItemType::class,
                'allow_add' => true,
                'allow_delete' => true,
                'prototype' => true,
                'label' => 'Some label',
                'by_reference' => false,
                'error_bubbling' => false,
                'entry_options' => [
                    'empty_data' => new CustomItem(),
                    'by_reference' => false
                ]
            ]
        );
    }

    public function configureOptions(...)
    {
        $resolver->setDefaults(['data_class' => CustomContainer::class]);
    }
}

If you need more information just ask and I will provide. I've implemented the adding of the subforms using jQuery and the prototype provided by the CollectionType class. I followed the tutorial on symfony.com and I'm at a loss at this point on what direction to go in. Any help is appreciated.

I did do some debugging of the form and the data is submitted correctly but when it goes to hydrate the form object it passes the last item in the form for every entry that was added.

EDIT

function addItem(e) {
    e.preventDefault();

    var collectionSelector = $(this).data('collection-selector');
    var list = $(collectionSelector);

    var counter = list.data('widget-counter');

    var newWidget = list.data('prototype');
    var newHtmlElem = newWidget.replace(/__name__/g, counter);

    var newElem = $(newHtmlElem);

    newElem.on('click', e.data.removeButtonClass, {itemContainerClass: e.data.itemContainerClass}, removeItem);

    counter  ;
    list.data('widget-counter', counter);

    var accordianContainer = $(collectionSelector   ' '   e.data.collapse);
    newElem.appendTo(accordianContainer);
}

$(document).ready(function () {
    $('body').tooltip({selector: '.custom-tooltip'});

    $('.add-custom-item-btn').click({removeButtonClass: '.remove-custom-item-btn', itemContainerClass: '.custom-item-item', collapse: '#custom-item-accordion'}, addItem);

    $('.remove-custom-item-btn').click({itemContainerClass: '.custom-item-item'}, removeItem);
});

This is a snippet of the twig template

{% macro printItem(custom_item, index) %}
<div >
    <div  role="tab" id="custom-item-heading-{{ index }}>
        <h4 >
            <a role="button"
                data-toggle="collapse"
                data-parent="#custom-item-accordion"
                href="#custom-item-collapse-{{ index }}"
                
            >
                Custom Item
            </a>
        </h4>
    </div>
    <div id="custom-item-collapse-{{ index }}"
        
        role="tabpanel"
        aria-labelledby="custom-item-heading-{{ index }}"
        aria-expanded="false"
    >
        <div >
            {{ form_row(custom_item) }}
            <button type="button" >
                <span  aria-hidden="true"></span>
            </button>
        </div>
    </div>
</div>
{% endmacro %}

{% import _self as formMacros %}

<div >
    <div >
        <button type="button"  data-collection-selector="#custom-container-fields-list">
            <span  aria-hidden="true"></span>
        </button>
    </div>
    <div id="custom-container-fields-list"
        data-prototype="{{ formMacros.printItem(form.collection.vars.prototype, '__name__') }}"
        data-widget-counter="{{ max(form.collection|keys)   1 }}"
    >
        <div  id="custom-item-accordion" role="tablist" aria-multiselectable="true">
            {% for index, custom_item in form.collection %}
            {{ formMacros.printItem(custom_item, index) }}
            {% endfor %}
        </div>
    </div>
</div>

Edit I've created a repo that demonstrates this issue with all the classes so you can view it at your liesure. No this is not the actual code but the issue still occurs with it. I'm using PHP 8.1 and symfony 5.4, but all of that is visible in the composer.json file in the repo below. If anyone can shed some light I would really appreciate it. Or if there is a consensus that this is a bug I'll log a bug with symfony but I would rather be sure of that first.

Repo that reproduces the issue

CodePudding user response:

Your problem seems to be caused by this line in your CustomType:

'empty_data' => new CustomItem(),

Since you've set empty_data to a single instance of CustomItem, this same instance is being re-used for each new row.

You've also set by_reference to false on the CollectionType, which means the adder method on your CustomContainer class is being called with this same CustomItem instance for each new row. Assuming you've used the make:entity command, this adder method only adds the CustomItem object if the same instance does not already exist in its array, which explains the missing items.

The solution is to create a new CustomItem instance for each new row, as in the example here: https://webmozart.io/blog/2015/09/09/value-objects-in-symfony-forms/ (under The empty_data Option)

'empty_data' => function (FormInterface $form) {
    return new CustomItem();
},
  • Related