18

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?

user3746259
  • 1,491
  • 2
  • 23
  • 46

3 Answers3

7

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.

kba
  • 4,190
  • 2
  • 15
  • 24
  • I thought of "rerendering / refreshing" only the collection part div after saving the new items .. but how could I do it the best way? How can I only refresh the collection part ? I've already took a look at these two issues a few weeks ago when I needed to validate the collection items, this part is working fine I just need to reindex / refresh the collection items, so I would love to hear you're solution on how you would do the refreshing to give you your earned bounty =) – user3746259 Jul 02 '15 at 10:45
  • I mean would you just create the whole div with adresses on the Server side or pass the New adresses as a json to client and create there in the js part the new container / div ? – user3746259 Jul 02 '15 at 14:52
  • Well, it depends on what's better for you (html vs. json). You have three options. Create snippet in response on save ajax request. It probably requires some magic with indexes in form. Or you can issue next ajax request after save request, create clear form and get snippet. And last one is reindexing in javascript (but be sure you know how is collection ordered by default on server). Unfortunately, I have not working code. As I said - it is a pain and if anyone know good solution I am listening. – kba Jul 08 '15 at 20:27
3

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;
}
Zoup
  • 101
  • 1
  • 5
  • This sounds like a nice way to do it too, I'm now simply reindexing the form fields after a successful submission which is working reliable either – user3746259 Mar 04 '16 at 10:35
1

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.

Community
  • 1
  • 1
tftd
  • 16,203
  • 11
  • 62
  • 106