Passing Arguments From Controller To Form Type In Symfony 3
This week I have been updating my Pray For News app that I built for my church, and other churches and groups to use. The app is built in Symfony2 and currently is in version 2.7. I am updating the framework to Symfony 3.1 and removing all the deprecations. Most deprecation notices are easy to eliminate and deal with forms.
If you are updating your app and you find a deprecation notice that says:
Passing type instances to FormBuilder::add(), Form::add() or the FormFactory is deprecated since version 2.8 and will not be supported in 3.0. Use the fully-qualified type class name instead.
Likely you are instantiating a new instance of your form like new YourFormType()
. This was fine up until version 2.8 and is no longer the way to get an instance of your form. Unless you make your form into a service, which many times I think is a good idea because you can easily inject dependencies.
The deprecated way of instantiating a form
In my case, I was passing dependency arguments to the form’s __construct
method from my controller. Below is an example of the old way to do things.
The controller:
$form = $this->createForm(new TeamType($currentOrg), $entity, array( 'action' => $this->generateUrl('teams_update', array('id' => $entity->getId())), 'method' => 'PUT', ));
The form type:
class TeamType extends AbstractType { private $organization; public function __construct(Organization $currentOrg) { $this->organization = $currentOrg; } ... }
The updated way of instantiating a form
The new and correct way of getting an instance of your form since Symfony 2.8 is to pass the fully qualified name of the form’s class. For example, pass FormType::class
or Namespace\YourAppBundle\Form\YourFormType
to the createForm
method.
You can see the problem if you wanted to inject dependencies to the construct. In my case, I needed to send the user’s current organization entity that they are logged in under.
$form = $this->createForm(TeamType::class, $entity, array( 'action' => $this->generateUrl('teams_update', array('id' => $entity->getId())), 'method' => 'PUT', 'currentOrg' => $currentOrg->getId(), ));
So what to do when you can no longer pass dependencies to the construct? You can pass them in the array options to the buildForm
method! Do this in the third argument of the createForm
method in the controller, like the example above.
Then in your form type, you can grab the arguments, like below.
/** * @param FormBuilderInterface $builder * @param array $options */ public function buildForm(FormBuilderInterface $builder, array $options) { $this->currentOrg = $options['currentOrg']; ... }
You also must declare your options in the setDefaults
method of the configureOptions
function. Be sure that you are using the new OptionsResolver
class and not the old OptionsResolverInterface
class. For good measure I am also setting the setRequired method and the setTypes() method as well.
/** * @param OptionsResolver $resolver */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'data_class' => 'Namespace\AppBundle\Entity\Team', 'currentOrg' => 1, ]); $resolver->setRequired('currentOrg'); // Requires that currentOrg be set by the caller. $resolver->setAllowedTypes('currentOrg', 'integer'); // Validates the type(s) of option(s) passed. }
Easy enough, right? A special thanks to this StackOverflow question for reference. Leave your comments and any additional knowledge or tricks you have for Symfony 3 forms below!
8 Comments
If you look at the examples in the symfony documentation, https://symfony.com/doc/current/cookbook/form/create_custom_field_type.html#creating-your-field-type-as-a-service, the right way of doing this is not that easy. Certainly when you need an entity in the constructor.
To make the form type unit testable I would make the constructor as follows:
In the buildForm method the code will be:
The thing that bothers me the most in your example is that typehinting is thrown out of the window. With my code there is always an instance of Organization in the buildForm method.
.
Hi David, thank you for your comments! I’ve worked on many different projects where making the form type into a service was needed. However, in this case, I disagree that it is the “right” way of doing it. My example here is to show how to do it from the controller, which is likely the majority of use cases. That being said, if I was going to use this form type in an event listener, or form subscriber, or as an embedded collection in another form, or in some other service, then I think it makes sense to go ahead and register the form as a service in the container. Again, for this scenario I think it is overkill to register it as a service.
Also, I think you mean to say
__construct()
not__constructor()
. In this case, if I were to make this form a service, passing the entire entity manager would not be necessary because you could set the form instance’s organization id easily by creating a private method which would set it.Secondly, the form is absolutely unit testable, as it would fail if the ‘currentOrg’ key wasn’t set in the default options.
That is the beauty of the OptionsResolver component/class. As far as type hinting being “thrown out of the window”, one could implement the
setAllowedTypes()
method inside the configureOptions function. Please see documentation on that here. I’ve updated the post to reflect this.Hi
I was using symfony 3 in all the forums i found to pass any ‘option’ name form controller, But this is not the case with symfony 3.
Thanks for this great tutorial I spent lot of time looking for it.
Keep up the great work.
Thank you Abdul!
I was having a terrible headache trying to send data to my form class. Finally I found this site and you saved my day THANKS!
I am glad to hear that! Thank you!
Thank you. i spent a lots of time on this problem.
You’re welcome! I’m happy it helped you!