Form composition in symfony2

Posted on by khepin

spacer 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 (followed by 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.

As I’m currently trying to get some time to learn symfony2, I went through some basic things in the form framework and found some things I’m not sure how to do. The form framework now seems incredibly powerful though, so I probably missed some things on “how to do” the things I’m trying to do.

What I want to build

I decided that for this first shot with symfony2 I wanted to build a small Facebook application. And as I like to play golf, my application is going to be about it.

I want an application that will publish to my Facebook feed that I played golf on that day, at that place, and what my score was. For this I need to be able to create golf courses (these hold very basic information: name and city). Then I need to add the holes of that golf course. A golf course can have (usually) 9 or 18 holes. Just after I create the course (name, city and number of holes), I want to add the standard scores for each hole. This is essential to later calculate how well a player did on that course.

I created an entity called “Hole” that is linked to a golf course and holds it’s order number (is it the first, seventh of thirteenth hole) and the standard score (the par in golf terms). So when I add holes for the new course, I want the form to display either 9 or 18 Hole form.

How I could do it

First I tried to build everything inside the controller. This form will be used in only one place, there won’t be much customization about it, I don’t really want to create a special form class just for it.

So basically I have one loop in which I had 9 or 18 “HoleType” to my form builder. And I add the 9 to 18 corresponding holes which already hold some data to an array. Then by setting this array as the data for the form and provided that the keys of the array are the same as the field names we gave while building the form, we pre-fill our form.

What I don’t like in this method is that I couldn’t find a way to pre-fill each sub-form while adding it to the global form. I add all the sub-forms first, and then set the data, so I’m going to loop over each sub-form once again to set its default data.

I would have liked to be able to do something like:

This to me looks much more clean and avoids looping over your fields all the time.

The “add” method of the formbuilder accepts a third argument that is an “options” array. In my code example here, I kind of suggest to put the data object as the third argument, maybe one of the possible options is to set the data, but I couldn’t find anything about that.

DI to the rescue!

As I was writing this post, I realized that setting the data in the HoleType controller is possible. Type (or Form) classes don’t inherit from anything, they only implement an abstract class. So everything is up to you on how you create those forms! So if you want to give your HoleType access to a Hole entity, you can! You can do it either through the constructor or through a “setHole()” method.

So if I change my “HoleType” class like this:

I can use my code just like I suggested in my previous example.

I’m still not happy!

I would like all those hole forms to be grouped under a special name in my form to allow me for easier display in the template. Right now the form contains 10 or 19 fields: all the holes (actually each of them contains 3 fields, but that’s not my point), plus a csrf token for security. I would like all the hole fields to be grouped under a larger ‘holes’ property of my form. Then in the template I could do something like:

This way I can deal with bigger parts of my form separately for the display. I can focus on displaying just what I want and have everything like the csrf token taken care of automatically at the end. The only way I found to do this for now was to create a bigger HoleSetType in which I have my 9 or 18 holes, then I can add it to the form with:

Here again, we inject the Course object to the HoleSetType, which then can build the form anyway it needs, adding 9 or 18 holes depending on what the Course is supposed to have. Also all the default data can be added within that class without having to deal with it in the controller.

The way I would love it is to be able to have a GroupFieldType for example to which I could add as many fields as I want on the fly and then add it to the form builder with $form->add(‘my_group’, $group_field);

I have not found a way to do this. I’ve tried using a second form builder to first add all of the holes and then send this under a different name. If anyone has any idea on how this can be done, I’m interested!

This entry was posted in Uncategorized by khepin. Bookmark the permalink.

14 thoughts on “Form composition in symfony2

  1. spacer Diego Luces on said:

    Did you try embedding a CollectionType that is composed of HoleType forms in your HoleSetType? As described in: symfony.com/doc/current/book/forms.html#embedding-a-collection-of-forms
    I think that is what you need here. It allows you to add as many subforms (Hole’s) to the parent form, even via Ajax, or to remove existing.

    Reply
    • spacer khepin on said:

      Well yes I’ve seen it and it absolutely seems to be what I need. However the article is empty and I have found nowhere an explanation on how to do this.
      I’m left with the code of that class and so far haven’t been able to really make a breakthrough!
      Any idea where this article has gone?

      Reply
  2. spacer Dylan Oliver on said:

    I have looked many times and never seen any content in the CollectionType cookbook entry.

    Reply
    • spacer khepin on said:

      I have found how to at least make basic use of it. Will make a new post soon.

      Reply
  3. spacer cordoval on said:

    this is my contribution to your thinking

    www.craftitonline.com

    check the ozmerk’s famous model form blog post using the search bar

    and discuss it there please, I will repost your post soon hopefully if I have time with a use case of mine too, your problem is interesting and we need to document how to properly use forms in a way that is dignifying to the learner spacer

    in that i am with you all the way

    Thanks

    PS> install the plugin for comments via email notification. Thanks for contributing with good writing too. Please email me if you reply here. Thanks!

    Reply
  4. spacer cordoval on said:

    great saw your response. Now trying to digest some of this. Thanks! i will subscribe to the discussion.

    Reply
  5. spacer Bernhard on said:

    You should never, ever pass a concrete model instance to your FormType like this:

    class HoleType extends AbstractType {
    public function __construct(Hole $hole = null) {
    $this->hole = $hole;
    }

    This will lead to inconsistent behavior. Use the second parameter of createForm() instead:

    $form = $this->createForm(new HoleType(), $hole);

    Reply
    • spacer cordoval on said:

      check this blog post and comment please

      www.craftitonline.com/2011/08/the-donts-of-symfony-2-di-on-abstract-classes/

      Reply
    • spacer khepin on said:

      Hey Bernhard, yeah thanks. All I can say is I already felt “not too comfortable” with it as it was already forcing me to work around it, but this was before I discovered the ability to pass data in an options array as a third parameter.
      I’ll try to refactor / update my code, put up a new post and warn readers at the beginning of my last 2 posts that they should not do it the way it’s described here!

      Reply
      • spacer cordoval on said:

        you should put up a notice saying it is a bad practice BUT IF THEY WANT TO REALLY LEARN THE THOUGHT PROCESS which is very important even more important that they follow you all the way with just that notice, that it is not the best practice but they will learn the WHY the best practice is there. IT is the best.

        Reply
    • spacer khepin on said:

      Just to check, in the case of ParSetType, is it an issue to pass the Course entity to it? Because in that case this is only used to build the form, but no “Course” data is set so it seems it’s not that much of an issue. It would be something like this.

      As I never allow the user to set the course id himself, it seems “ok”, though still not sure if it’s a good idea…

      Reply
      • spacer Bernhard on said:

        It still is an issue. Keep in mind that Form::setData() and Form::bind() can be called numerous times in the lifecycle of a form. If you make your form’s construction dependent of its data, things will break once setData() is called again later (which it often is, for example when data is set on the parent form).

        The correct way to do what you want to achieve is to use a listener (for an example see https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php). By listening to the FormEvents::PRE_SET_DATA or the FormEvents::POST_SET_DATA event you can adapt your form to its data every time that data is changed.

        Reply
  6. Pingback: The DON’Ts of Symfony 2 DI on Abstract Classes | Craft It Online!

  7. Pingback: Finally Through: symfony2 forms and CollectionType, make it dynamic! | Synofony

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

*

*

You may use these HTML tags and attributes: <a class="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>