9

In Symfony2, if I embed a collection of forms pointing at a many to one relationship in Doctrine and allow adding and deletion, if I delete a record from the beginning, add one at the end, and edit some in the middle how does the system know which records to update with which data?

There is nothing in the tutorial that passes the primary key of the embedded data around. Under certain circumstances, my records are getting needlessly deleted and added again rather than edited in place (even if there are no changes to the particular record). This breaks the fields on the records that are not included on the form, setting them to their default values from the DB model.

Is there a way to pass the primary key in the form and have it used to perform updates when the data comes back?

5
  • Did you look a the sections "Allowing new tags.. " and "Removing tags..." in this doc yet? Commented Mar 7, 2013 at 14:58
  • @james_t Yes. I have allowed both of them. Commented Mar 7, 2013 at 15:20
  • Try adding the id as a hidden field on the embedded form type! Commented Mar 8, 2013 at 7:36
  • @hacfi: Then I get Property "id" is not public in class "...". Maybe you should create the method "setId()"? on submission of the form. Commented Mar 8, 2013 at 10:28
  • @SteffenBrem No, not really. I was able to work around it by using individual forms for each item, rather than a form collection. I added ID fields to the requests, then deleted them with unset before passing the request data to the form. Commented Dec 8, 2014 at 0:37

3 Answers 3

4

If you want to index the collection (by the entity id) for all querys, you can simply use the indexBy annotation in your entity class.

/**
 * @ORM\OneToMany(targetEntity="EntityClass", mappedBy="EntityVariable", indexBy="id")
 */
private $collection;
Sign up to request clarification or add additional context in comments.

5 Comments

But then how is this info used in the controller when updating?
The form framework handles this automatically. Instead of indexing the collection items by numbers 1,2,3..., the collection is then indexed by the id of the entities. Since the index is both on the entities fetched from the db and the ones send back via POST, the framework knows which formdata belongs to which entity.
OK makes sense. Do you know how this could work for entities with Composite Keys? Since there are actually two ID's and not one?
I think this is not possible with composite keys. I first setup my database scheme with multiple composite keys, but then just introduced an id column, since this caused to many problems with the ORM.
@Akkumulator which entity does this need to be put in? The one I am grabbing the ID's from or the entity holding them?
1

Based on the Akkumulator's answer and comment and some experimentation, I did this:

Create new fields (using Javascript as described in the documentation) with __name__ replaced not by a number but by a string: new_ followed by an forever increasing number that has nothing to do with the list (e.g. new_1, new_2, new_3...)

I don't have to push the primary keys into the forms and I don't need indexBy either - that's good, because indexBy felt like it was too far removed from the form, ending in having the Action at a distance anti-pattern.

Why this works:

  • PHP arrays aren't like those in other languages. They're always dictionaries, so you can add string keys to them even if they only have numeric keys to start with.
  • Because the Symfony collection is mapped by field name, new fields will not match existing data and deleted fields will not be matched to existing data (and thus be removed from the set)

Comments

0

One way to pass primary id is to use INDEX BY.

For example, say I have an entity called Customer and a Customer has several Emails. In my Customer repository class, I can specify my collection to be indexed by Email's primary id.

$qb->select('c, e')
    ->leftJoin('c.emails', 'e', null, null, 'e.id')
    ->where('c.id = :id');

By doing so, the generated name of the input tag would be

customer[emails][e.id][fieldName]

Upon submitting the form, Symfony will bind the request values according to the input names.

4 Comments

If the input names "match" what? Are the managed entities stored in the session or something? If so, what happens if I open two different windows? If not, what if a row has been deleted or added in the database by someone else between me showing the form and getting it again? The way to match a row in a relational database is by it's primary key. That is a primary key's purpose. The primary key is not included anywhere in the form (making them part of the input names would be a good place).
You're absolutely right. I've edited my answer to include primary key.
you are a star suke! do you know how many days I have spent looking to change my output of customer[emails][incremental_key][fieldName] to customer[emails][database_record_id][fieldName] - 3 days, trying all sorts to change the output of the form builder - all along I was looking in the wrong place - thanks!!!
This is great, but why do you believe "Upon submitting the form, Symfony will bind the request values according to the input names"?You haven't told the form anything at this point. Also, what input name do I use for newly added rows? I can't know what the next MySQL auto-increment will be.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.