User dependent forms in symfony2

In a lot of cases it can happen that you want to make your forms different based on the user currently browsing your app.

The cookbook currently shows how to change a form with event subscribers depending on the data in that form. Say creating a record rather than updating something could require a different form for example.

Here we’re gonna take a look at what happens when your form should be customized based on something that is not readily available in the form’s data. Such as the user data.

In many cases though I’d tell you it’s better to not have to do that. There’s a lot of UI ways to make things so you never need the form to hold user specific data. Like letting a user browse to the thing she’s trying to update rather than have a huge select box inside of the form. But if you need something like that, here’s a clean way to do it!

What’s wrong with the cookbook?

The cookbook article on dynamically generating formsĀ currently shows an example like this:

It doesn’t really matter for now what the subscriber actually does. The important thing is that we create a subscriber class and add it to the form. Then, when some specific form event is triggered, the subscriber will be allowed to perform some actions on the form.

The issue is that we created the subscriber inside of the form class. So doing like this, it’s impossible to get access to the user, or to your database, or to whatever else you could think of. Everything else works wonders here, just how to access some other services???

This is gonna sound boring

Because like all the time in Symfony2, the answer is gonna be:

 

 

Dependency Injection, you guessed it!

Form types can (and will) be defined as services. And our subscriber can, and will also be defined as a service!

Our form type only changes in that we add a constructor that takes our event subscriber as a parameter. Let’s say we want a form to send a message and the user should only be able to send messages to her friends and not any user on the website:

Now let’s take a look at our subscriber:

So before the data is set in the form we:

  1. Create our form options for a ‘document’ type field (or ‘entity’ when using an SQL database)
  2. Set the query builder option (this is the interesting part)
  3. Add a ‘document’ field with these options to our form under the name ‘friend’

The query builder part is the interesting one because it takes the user id as a parameter. This wouldn’t be available in the form otherwise.

Also take a look at the constructor. It takes the security context as a parameter (as this will allow us to retrieve the current user), which is not available in the form, so we couldn’t directly create this object from within the form like is done in the first example of this article. We need to define services for these two now. And quite simply, in your config.yml, all you need is to add:

The form takes the subscriber as a parameter, and the subscriber will be instantiated with the form factory and the security context.

Tying it all together in the controller

There you go. Your forms can now be customized on any service / data even outside of the form itself.

 

15 thoughts on “User dependent forms in symfony2

  1. Nice article. I propose to inject the SecurityContext directly into FriendMessageFormType though. The event subscriber is an internal implementation detail that the outside world should not care about nor does it make sense to replace the subscriber by a different implementation.

    When you inject SecurityContext directly, you can even replace the subscriber by a simple closure.

    • In this case, we’ve created a form field called: acme_friend_message.
      When we defined the service, we tagged the form type with ‘form.type’ and alias ‘acme_friend_message’.
      This means that from now on, your type can be used as any other form type when building the form. The same way that you would add a text input, you can add a friend message input that will bring all of its fields and configurations with itself. See: https://gist.github.com/3903414

  2. Very nice tutorial!

    I got a simple question relating this topic. So is there a way to say ok let’s add the entity field ‘xy’ with with object $xy as value (right after the user sent/submit the form)?

    It mean $data = $event->getData(); gives an object with field ‘xy’ included but an empty value ”. So there must be an easy way to say setValue of $data->user to ‘xy’, right?!

  3. Very nice tutorial!

    I got a simple question relating this topic. So is there a way to say ok let’s add the entity field ‘xy’ with with object $xy as value (right after the user sent/submit the form)?

    It mean $data = $event->getData(); gives an object with field ‘xy’ included but an empty value ”. So there must be an easy way to say setValue of $data->user to ‘xy’, right?!

  4. Ok, it’s nice but what about FriendMessageFormType with 3-4 events Subscribers? Do i have to pass all subscribers as parameters in constructor? It would be nice to pass an array of subscribers to constructor but than how to set the service?
    And what about using FormEvents and EventListeners: http://www.kamiladryjanek.com/2012/10/symfony-2-1-and-forms-dependent-fields-recommended-solution/ Are there any advantages / disadvantages of using listeners instead of subscribers?

    • Data transformers are something different. They change the PHP representation of the data. For example a date field on the php side could be a string, an array (year / month / day), a DateTime object etc… On the HTML side of things, it could be an int (timestamp) field, an array, a string, but not a DateTime.

      Data transformers on custom types help you deal with just that: changing from one format to the other.

      Now, if you were to create your custom CurrentUserFriendType, inside of that type definition, you still need access to the user object. So you’d just be pushing the same thing to a different form type… A more re-usable one, that’s for sure and it’s a good thing. But I believe the technical solution on populating the options of that form would still be what I described here.

      • Thanks Khepin, for your reply, it is realy useful for me.
        I am asking questions, because I want learn from you.

        The descriped solution, is applicable to more than one type so I have two problems:
        * Options of formOptions is not changable, How is possible to change one option!?
        * Field name (friend) will be same for all forms, How we can change it?

        • You can use $formFactory->createNamed() to choose the name of your field in the form. But if you’re embedding a collection of friend fields, then the CollectionType will take care of naming them 1, 2, 3 etc…

          You can always pass options to a form, they will be merged with the default ones.

  5. Hi Khepin,

    I am not sure what I am doing wrong, but this is not working for me.
    I use symfony 2.2.1 and I follow exactly what you described with EventSubscriber. I use two form events, i.e. PRE_SET_DATA and PRE_BIND.

    My case is I have two entity field type as dropdown. When the value of the first dropdown is changed, it makes an AJAX request and populate the second dropdown (multiple select). However when the form is submitted, both dropdowns are not submitted.

    I can still see the value of these dropdown in the PRE_BIND, and using the injected entitymanager, I can query the objects for each dropdown value and then add the second dropdown field to the form.

    In my controller, I can see the raw data in the request object, from $request->request->get($form->getName());
    It has all the submitted data including the token and those two dropdown values, but as soon as the form binds the request $form->bind($request), when I dump $form->getData(), I see array()

    Any ideas?

    Thanks

    Kal

Comments are closed.