Note
This post addresses latest versions of Symfony 2. In Symfony 3 form instantiation changed, but just a little bit, so most concepts of this post will still be valid.Sometimes you stumble upon the case when there is need to perform some extra logic while creating or handling a form. Best way to do it without getting your code shattered is to inject services into the form.
What is Symfony form?
In fact, all classes that extend AbstractType are not forms - they are form factories. Real form class is created when you call createForm() method inside your controller. Example:
This is a form definition (this is the reason that it is so popular to name Symfony forms as *Type, because they are form types, not forms themselves).
Real form is created when you do this:
As you can see, after calling controller's createForm() method, Form (not FooType) instance will be returned. Under the hood, your controller calls form.factory service, which in the end calls buildForm() method in your FooType to build the form. This description is of course simplified a bit, if you want to go deeper just click over some sample project and check out The Form Component documentation page.
Why do people create *Form classes instead of *Type classes?
This is just a matter of fashion. Some people respect that what they are writing is a form type definition, while rest is used to create Form classes from other frameworks. I fall into the second group.
Injecting things
Okay, enough theory, let's describe three approaches of injecting things into your form factory.
Worth noting
When you get into AbstractType's guts, you will notice that there is no constructor defined. That gives us field to easily inject stuff using constructor injection.The ugliest: inside controller
The proper: defining form as a service
This is another approach, way better than the first one. This is the way that most of people go. Create your form type, and then define it as a service:
Having this, you can build your form like this:
Pros:
- cleaner controller
- container handles injection
- thanks to tags, form factory knows where to look for form
Cons:
- you cannot inject non-service stuff in here
- one form type = one service. This is bad on bigger projects
By the way, there's a guy named Joe Sexton who got deeper into this approach on his blog post.
The efficient: Have your own form factory
This is the way that I prefer doing it. Speaking marketing language, let's start with advantages:
container handles injection
- you can pass non-service things
- you can make few factories in single service, which is the desired way for bigger projects
- you can even pass form.factory service into your factory, so you can return Form instances directly from your service (no need to call createForm() in controller
Okay, so let's say that we write ticket booking system and we have those three forms:
All of above take part in one flow: ticket booking. It is natural to create one service that will handle creation of all of them. Let's create a service then:
And this is service definition:
Now, to get the form:
I hope that you will find above stuff helpful. Happy coding!
Now, to get the form:
I hope that you will find above stuff helpful. Happy coding!