Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ArrayCollection (Collection of forms) index collision in Symfony 2

I am using Symfony2 to build up my page. When I try to update a collection of forms (like described in the cookbook entry "How to Embed a Collection of Forms"), i get a collision of the indexes of the frontend and the indexes of the ArrayCollection in the backend.

I've got the relation User <-> Address (OneToMany). A user wants to create/update/delete his addresses, therefore he can add / delete in the frontend with the help of the javascript part new address elements. He does the following:

(1) Adds new address (has index: 0)

(2) Adds new address (has index: 1) and instantly removes this address again

(3) Adds new address (has index: 2).

When he clicks on save button, the following code saves/updates the user (and its addresses):

 $this->em->persist($user);
 $this->em->flush();

New addresses for example are then correctly persisted to the database. Now the user wants to update the address e.g. with index 0. When he now clicks on the save button, it updates the adress with "index 0", but at the same time, it adds again the address with "index 2" to the database (object). To better understand the problem, i've drawn a small illustration (handmade, sorry for my bad art skills):

Collection error image Now , i've got two times the address with "index 1" within my object / database. I know why this happens, it's because the first "index 1" address gets mapped to the ArrayCollection element "number 1", and the second gets mapped to "number 2 "(because of the frontend name "index 2"). You can say: "it just fills up the addresses, until it reaches the frontend index in the backend".. But how can I fix this behaviour ?

Site note: This behaviour occurs using ajax requests, because if you would reload the page after clicking "save button", it would reindex the addresses in the frontend correctly with the indexes in the backend.

My suggestion to handle that situation:

Reindexing the frontend indexes after clicking save with the server side indexes. Is this a clear / the only solution for my problem?

like image 348
user3746259 Avatar asked Jun 27 '15 16:06

user3746259


3 Answers

Yes, this is problem of Symfony form collection and it has no easy solution imho. But I have to ask why don't you do exactly the same thing what page refresh does? You can refresh only html snippet with collection. HTML code for snippet can come from server-side. Back to your question - yes, reindexing is good solution until you do not want to try write custom collection type on your own.

symfony/symfony/issues/7828

There is similar problem with validating in collection - symfony/symfony/issues/7468.

Well I think default collection type and the tutorial in Symfony docs has the some drawbacks. Hope that's help.

like image 101
kba Avatar answered Nov 16 '22 06:11

kba


I have come round this issue on the client side by modifying the Javascript/Jquery code given in the Symfony Documentation.

Instead of numbering the new elements by counting the sub-elements, I am looking at the last element's id and extracting its index with a regular expression.

When adding an element, I am incrementing the last index by 1. That way, I never use the same index.

Here is my code :

// Initializing default index at 0
var index = 0;

// Looking for collection fields in the form
var $findinput = $container.find(':input');

// If fields found then looking for last existing index
if ( $findinput.length > 0 ) {

    // Reading id of last field
    var myString = $findinput.last().attr('id')

    // Setting regular expression to extract number from id containing letters, hyphens and underscores
    var myRegex = /^[-_A-Za-z]+([0-9]+)[-_A-Za-z]*$/

    // Executing regular expression on last collection field id
    var test = myRegex.exec(myString);

    // Extracting last index and incrementing by 1
    if (test.length > 0) index = parseInt(test[1]) + 1;
}
like image 44
Zoup Avatar answered Nov 16 '22 06:11

Zoup


I ran into this problem a couple of times during the past two years. Usually, following the Symfony tutorial How to Embed a Collection of Forms does the job just fine. You need to do a little bit javascript coding to add the "edit/update" functionality, but other than that - you should be just fine using this approach.

If, on the other hand, you have a really complex form which uses AJAX to validate/save/calculation/business logic/etc, I've found it's usually a better to store the final data into an array in the session. After submitting the form, inside the if($form->isValid()){...} block, you would have

$collection = new ArrayCollection($mySessionPlainArray);
$user->setAddress($collection);

I would like to warn you to be careful with the serialization of your data - you might get some awkward exceptions or misbehavior if you're using entities (see my question).

I'm sorry I can't provide more code, but the solution to this problem sometimes is quite complex.

like image 1
tftd Avatar answered Nov 16 '22 06:11

tftd