If you are working with forms in Symfony, you know how easy is to implement a new Type for any entity so you can create easily a CRUD form operation in your application. Sometimes, you may want to add dinamically fields into your form from a controller, because you don't want this field to appear on another as it's only needed in this case. This can be achieved thanks to the mapped property of a form field.
Let's imagine that we have an UserType
to create a Form to register an user in our application:
<?php
namespace userBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
class UserType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class , array(
"attr" => array(
"class" => "form-control"
)
))
->add('username', TextType::class, array(
"attr" => array(
"class" => "form-control"
)
))
->add('description', TextareaType::class, array(
"attr" => array(
"class" => "form-control",
"maxlength" => 255
)
))
->add('password', RepeatedType::class, array(
'type' => PasswordType::class,
'invalid_message' => 'The password fields must match.',
'options' => array(
'attr' => array(
'class' => 'form-control'
)
),
'required' => true,
'first_options' => array('label' => 'Password'),
'second_options' => array('label' => 'Repeat Password'),
))
;
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'userBundle\Entity\User'
));
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'userbundle_user';
}
}
This type contains 5 requires fields from an UserEntity
that will be processed in our controller to create a new user. In our newAction
of the controller we'll create a form with the following code:
<?php
public function newuserAction(Request $request){
$user = new User();
// Create a form using the UserType
$form = $this->createForm(UserType::class, $user);
// Add dinamically a new multiple select field that isn't in the UserEntity:
$form->add('roles_options', ChoiceType::class, array(
"mapped" => false,
"multiple" => true,
"attr" => array(
'class' => "form-control"
),
'choices' => array(
'Blogger' => 'ROLE_BLOGGER',
'Administrator' => 'ROLE_ADMIN'
)
));
$form->handleRequest($request);
if ($form->isSubmitted()) {
// Rest of the logic
}
return $this->render('users/new.html.twig', array(
'form' => $form->createView()
));
}
Now if the user access the new route, he will see a form that he only needs to submit. Once submitted, the same newAction will be triggered (if configured in routing.yml with POST) and your logic to process the form will be executed. The advantages of creating a Form with Symfony Types is that you can persist easily an Entity in your controllers, however you will need sometimes to make the processing step dynamic in the controller by modifying some values (or using them for another purpose) so you can easily retrieve the values of the form easily using the getData
method of the form:
<?php
if ($form->isSubmitted()) {
$data = $form->getData();
// $data contains an array like:
// array(
// "name" => "The submitted name",
// "username" => "The submitted Username",
// "description" => "The submitted description",
// "password" => "The submitted password"
// );
}
But wait, where the **** is the value of the roles_options
field that we add dinamically in our controller? It isn't inside the real form, simply because the field isn't mapped, so it can't be inside the real form because that would trigger another exception namely 'This form should not contain extra fields'
.
Retrieve value of non-mapped field
Instead of access the value from the form array data, you need to use the get method from the form that expects as first argument the name of the field that wasn't mapped. From the returned object, you can retrieve the value using the getData
method:
<?php
if ($form->isSubmitted()) {
$data = $form->getData();
// Retrieve the value from the extra field non-mapped !
$roles = $form->get("roles_options")->getData();
// Where $data contains an array like:
// array(
// "name" => "The submitted name",
// "username" => "The submitted Username",
// "description" => "The submitted description",
// "password" => "The submitted password"
// );
// and $roles another array in this case with our options (that were selected obviously):
// [
// 0 => "ROLE_BLOGGER"
// 1 => "ROLE_ADMIN"
// ]
}
Happy coding !