Skip to content

Commit 9ef4ad3

Browse files
committed
add cookbook entry on creating dynamic forms based on services
1 parent 6e69f44 commit 9ef4ad3

File tree

1 file changed

+165
-0
lines changed

1 file changed

+165
-0
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
.. index::
2+
single: Form; Events
3+
4+
How to Dynamically Generate Forms based on user data
5+
====================================================
6+
7+
Sometimes you want a form to be generated dynamically based not only on data
8+
from this form (see :doc:`Dynamic form generation</cookbook/dynamic_form_generation>`)
9+
but also on something else. For example depending on the user currently using
10+
the application. If you have a social website where a user can only message
11+
people who are his friends on the website, then the current user doesn't need to
12+
be included as a field of your form, but a "choice list" of whom to message
13+
should only contain users that are the current user's friends.
14+
15+
Creating the form type
16+
----------------------
17+
18+
Using an event listener, our form could be built like this::
19+
20+
namespace Acme\WhateverBundle\FormType;
21+
22+
use Symfony\Component\Form\AbstractType;
23+
use Symfony\Component\Form\FormBuilderInterface;
24+
use Symfony\Component\Form\FormEvents;
25+
use Symfony\Component\Form\FormEvent;
26+
use Symfony\Component\Security\Core\SecurityContext;
27+
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
28+
use Acme\WhateverBundle\FormSubscriber\UserListener;
29+
30+
class FriendMessageFormType extends AbstractType
31+
{
32+
public function buildForm(FormBuilderInterface $builder, array $options)
33+
{
34+
$builder
35+
->add('subject', 'text')
36+
->add('body', 'textarea')
37+
;
38+
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event){
39+
// ... add a choice list of friends of the current application user
40+
});
41+
}
42+
43+
public function getName()
44+
{
45+
return 'acme_friend_message';
46+
}
47+
48+
public function setDefaultOptions(OptionsResolverInterface $resolver)
49+
{
50+
}
51+
}
52+
53+
The problem is now to get the current application user and create a choice field
54+
that would contain only this user's friends.
55+
56+
Luckily it is pretty easy to inject a service inside of the form. This can be
57+
done in the constructor.
58+
59+
.. code-block:: php
60+
61+
private $security_context;
62+
63+
public function __construct(SecurityContext $security_context)
64+
{
65+
$this->security_context = $security_context;
66+
}
67+
68+
.. note::
69+
70+
You might wonder, now that we have access to the User (through) the security
71+
context, why don't we just use that inside of the buildForm function and
72+
still use a listener?
73+
This is because doing so in the buildForm method would result in the whole
74+
form type being modified and not only one form instance.
75+
76+
Customizing the form type
77+
-------------------------
78+
79+
Now that we have all the basics in place, we can put everything in place and add
80+
our listener::
81+
82+
class FriendMessageFormType extends AbstractType
83+
{
84+
private $security_context;
85+
86+
public function __construct(SecurityContext $security_context)
87+
{
88+
$this->security_context = $security_context;
89+
}
90+
91+
public function buildForm(FormBuilderInterface $builder, array $options)
92+
{
93+
$builder
94+
->add('subject', 'text')
95+
->add('body', 'textarea')
96+
;
97+
$user = $this->security_context->getToken()->getUser();
98+
$factory = $builder->getFormFactory();
99+
100+
$builder->addEventListener(
101+
FormEvents::PRE_SET_DATA,
102+
function(FormEvent $event) use($user, $factory){
103+
$form = $event->getForm();
104+
$user_id = $user->getId();
105+
106+
$form_options = [
107+
'class' => 'Acme\WhateverBundle\Document\User',
108+
'multiple' => false,
109+
'expanded' => false,
110+
'property' => 'fullName',
111+
'query_builder' => function(DocumentRepository $dr) use ($user_id) {
112+
return $dr->createQueryBuilder()->field('friends.$id')->equals(new \MongoId($user_id));
113+
}
114+
];
115+
116+
$form->add($factory->createNamed('friend', 'document', null, $form_options));
117+
}
118+
);
119+
}
120+
121+
public function getName()
122+
{
123+
return 'acme_friend_message';
124+
}
125+
126+
public function setDefaultOptions(OptionsResolverInterface $resolver)
127+
{
128+
}
129+
}
130+
131+
Using the form
132+
--------------
133+
134+
Our form is now ready to use. We have two possible ways to use it inside of a
135+
controller. Either by creating it everytime and remembering to pass the security
136+
context, or by defining it as a service. This is the option we will show here.
137+
138+
To define your form as a service, you simply add the configuration to your
139+
``config.yml`` file.
140+
141+
.. code-block:: yaml
142+
143+
acme.form.friend_message:
144+
class: Acme\WhateverBundle\FormType\FriendMessageType
145+
arguments: [@security.context]
146+
tags:
147+
- { name: form.type, alias: acme_friend_message}
148+
149+
By adding the form as a service, we make sure that this form can now be used
150+
simply from anywhere. If you need to add it to another form, you will just need
151+
to use::
152+
153+
$builder->add('message', 'acme_friend_message');
154+
155+
If you wish to create it from within a controller or any other service that has
156+
access to the form factory, you then use::
157+
158+
// src/AcmeDemoBundle/Controller/FriendMessageController.php
159+
public function friendMessageAction()
160+
{
161+
$form = $this->get('form.factory')->create('acme_friend_message');
162+
$form = $form->createView();
163+
164+
return compact('form');
165+
}

0 commit comments

Comments
 (0)