2011S
Basic Usage of the Symfony2 Collectiontype Form Field
UPDATE: This article was written while trying the form system and represents my thoughts at a certain stage, in order to discuss and get feedback. Not all info here is valuable and some is actually mis-leading. If you read this, make sure you read the comments as well, or wait for a coming article with more correct foundations.
Reading these articles in a series (starting with this one) will get you through most of the thought process, and help understand the reasons behind the good practices. Be sure to also read the comments discussion.
In my previous post, it seemed to me and to a few commenters that I needed to make use of the CollectionType inside my Symfony2 form. However the documentation on that specific field is non-existent to the day. This post will show how I used it, which might not be the perfect way, I am not sure. If some people “in the know” read this and think it’s correct, I’d happily write a bit of documentation for everyone to see.
Quick reminder on what I was trying to do
It would be an app related to golfing. Therefore you need to enter golf courses and the standard score (par) for each hole. Not all golf courses are made equal and some will have 9 holes where some other have 18. So the user will first enter basic details of the course including the number of holes and then I need to generate the appropriate form with 9 or 18 pars to be set. Adding all those fields seems the perfect place to use a collection type. Especially as I want them to be part of a “sub”-form in order to make the form rendering more easy.
The form type
At first I was thinking of completely building my form within the controller since there’s not gonna be many re-use of it. However in the end it doesn’t really make more sense to me over implementing a new form type. I called it ParSetType. And the code is as follows:
Update: as asked in the comments, here’s a link to the HoleType code so: the “number” is set as hidden because we set it for the user. The par is the real editable field. The Course is not set in the form because it was already added to the Hole class in ParsetType.
The constructor always takes a Course as an argument because we can’t set the par’s of … nothing and we can’t create the pars before knowing if we need to set 9 or 18 of them.
Now in the buildForm function, I set “holes” as a collection type field, then iterate over the number of holes that this course has and add a specific field HoleType (related to the Hole entity). Here I have a “problem” that I had to manually generate the sub-form’s field names as hole_1, hole_2, … I would rather provide a name format like hole[%s] or anything like this and have the form generate the names itself as I keep adding fields. (This might actually already be possible but I haven’t found how to set it).
Inside the for loop, the first 3 lines are just for me to set some data first. Because I already know some info about the hole that the user should not have to setup himself. The last line is the one that actually populates the collection. So it’s only an ->add(XXX) method to be applied on the sub-form.
Template
Having this set like that allows my template to be very clear easy and straight forward to render:
There’s only one thing interesting here that having set all my hole_$i sub-forms as part of the holes collection, I can render that part precisely as I want and iterate only on the fields included in that collection with {% for hole_form in form.holes %}
.
Controller
The controller to create the empty form is pretty straightforward:
But getting the results and saving to database is a bit more tricky. First a look at the code:
The basic steps are:
- Retrieve the course from the database based on the hidden form value we had. This has to be done before binding the data to the form because the form needs a Course in its constructor (so here I think the fault is on me and my design).
- Create a form and bind it to the data. When the form is created, the course object is already attached to each HoleType.
- Get each hole, link it to the entity manager and flush. Everything went ok, send the user to see what he just added.
- If it didn’t go ok, show him the form again with errors etc…
Now you’ve noticed there’s one more little step: removing the csrf token from the Collection field. This field does not appear in the HTML source when we iterate over each form.holes in the Twig template. However it is set in the Collection object as one of the subfields. I am not sure what the reason for that is, or what can be done about this, but for now it really doesn’t make sense to me. Especially as it is not used while outputting the form, it does not add any security at all. The one that is output in the HTML of the form is from the global ParSetType form.
Conclusion
That’s how I managed to make use of the CollectionType. I don’t really find it easy because of:
- The names that I have to set for each sub-form by myself.
- The CSRF token that is uselessly messing the save process as it’s considered a part of the collection as well. That CSRF is not transparent in my process…