I'm struggling with symfony forms. I want to build a form for an user. This user as a arraycollection field that gather products (nom, description).
I would like to create a form that create a checkbox for each of the products passed to the form builder. Actually it just created input fields without labels... Here is the code of my UserType class :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('nom')->add('prenom')->add('mail')->add('tel1')->add('tel2', TextType::class, array('required' => false))
->add('username')->add('password', PasswordType::class)
->add('groupe', ChoiceType::class, array(
'choices' => array('Administrateur' => 'ROLE_SUPER_ADMIN', 'Gérant' => 'ROLE_ADMIN', 'Opérateur' => 'ROLE_USER'),
'expanded' => true,
))
->add('produits', CollectionType::class, array(
'entry_type' => ProduitType::class
))
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => User::class
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'gestcoupons_userbundle_user';
}
Here is my ProduuctType code :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('nom')->add('prenom')->add('mail')->add('tel1')->add('tel2', TextType::class, array('required' => false))
->add('username')->add('password', PasswordType::class)
->add('groupe', ChoiceType::class, array(
'choices' => array('Administrateur' => 'ROLE_SUPER_ADMIN', 'Gérant' => 'ROLE_ADMIN', 'Opérateur' => 'ROLE_USER'),
'expanded' => true,
))
->add('produits', CollectionType::class, array(
'entry_type' => ProduitType::class
))
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => User::class
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'gestcoupons_userbundle_user';
}
Here is my UserController code :
public function ajouterAction(Request $request){
$this->denyAccessUnlessGranted('ROLE_ADMIN', null, 'Unable to access this page!');
$em = $this->getDoctrine()->getManager();
$produits = $em->getRepository('ProduitBundle:Produit')->findAll();
$societes = $em->getRepository('SocieteBundle:Societe')->findAll();
$user = new User();
foreach ($produits as $produit) {
$user->getProduits()->add($produit);
}
$form = $this->createForm('GestCoupons\UserBundle\Form\UserType', $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush($user);
return $this->redirect('dashboard_admin');
}
return $this->render('user/new.html.twig', array(
'user' => $user,
'form' => $form->createView(),
));
}
Thanks ahead for your help.
You should use Symfony\Bridge\Doctrine\Form\Type\EntityType instead of CollectionType.
In your UserType
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use AppBundle\Entity\Produit;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nom')
->add('prenom')
->add('mail')
->add('tel1')
->add('tel2', TextType::class, array(
'required' => false
))
->add('username')
->add('password', PasswordType::class)
->add('groupe', ChoiceType::class, array(
'choices' => array(
'Administrateur' => 'ROLE_SUPER_ADMIN',
'Gérant' => 'ROLE_ADMIN',
'Opérateur' => 'ROLE_USER'
),
'expanded' => true,
))
->add('produits', EntityType::class, array(
'class' => Produit::class,
'multiple' => true,
'expanded' => true,
'label' => 'nom' //If you don't have a __toString method in your Produit Entity
))
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => User::class
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'gestcoupons_userbundle_user';
}
This way you will have a checkbox for each Product entity in your database and only the one you checked will be associated to your user
Related
In a previous question (Symfony Check if at least one of two fields isn't empty on form validation) I had asked help for form validation using Callback. The answer given by #hous was right, but it doesn't work for elements in a CollectionType, reason why I'm opening a new question.
Based on the previous answer I have done the following:
Here is my "mother" Form:
class BookingVisitorType extends AbstractType
{
private $router;
private $translator;
public function __construct()
{
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('visitors', CollectionType::class, [
'entry_type' => VisitorType::class,
'label' => 'entity.booking.visitors',
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'by_reference' => false,
'entry_options' => [
'label' => false,
'delete-url' => $options['visitor-delete-url']
],
'constraints' =>[
new Count([
'min' => 1,
'minMessage' => 'validator.visitor.at-least-one-visitor',
'max' => $options['numberOfPlaces'],
'maxMessage' => 'validator.visitor.cannot-have-more-visitor-than-spaces',
'exactMessage' => 'validator.visitor.exact-message'
])
]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Booking::class,
'numberOfPlaces' => 1,
'visitor-delete-url' => ''
]);
}
}
Here is my "son" Form:
class VisitorType extends AbstractType
{
private $phone;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName', TextType::class, [
'label' => 'entity.visitor.first-name',
'constraints' => [
new NotBlank(),
new Length([
'min' => 2,
'max' => 255
]),
new Regex([
'pattern' => "/[\pL\s\-]*/",
'message' => 'validator.visitor.not-valide-first-name'
])
]
])
->add('phone', TextType::class, [
'label' => 'entity.visitor.phone-number',
'required' => false,
'constraints' => [
new Regex([
'pattern' => "/[0-9\s\.\+]*/",
'message' => 'validator.visitor.not-valide-phone-number'
]),
new Callback(function($phone, ExecutionContextInterface $context){
$this->phone = $phone;
}),
]
])
->add('email', TextType::class, [
'label' => 'entity.visitor.email',
'required' => false,
'constraints' => [
new Email(),
new Callback(function($email, ExecutionContextInterface $context){
if ($this->phone == null && $email == null) {
$context->buildViolation('validator.visitor.email-or-phone-required')->addViolation();
}
}),
]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Visitor::class,
'error_bubbling' => false,
'delete-url' => '',
]);
}
}
My "booking" (shortened) class:
/**
* #ORM\Entity(repositoryClass="App\Repository\BookingRepository")
*/
class Booking
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Visitor", mappedBy="booking", orphanRemoval=true, cascade={"persist"})
* #Assert\Valid
*/
private $visitors;
}
And finally my "visitor" (shortened) class:
/**
* #ORM\Entity(repositoryClass="App\Repository\VisitorRepository")
*/
class Visitor
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=45, nullable=true)
*/
private $phone;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $email;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Booking", inversedBy="visitors")
* #ORM\JoinColumn(nullable=false)
*/
private $booking;
/**
* #Assert\Callback
*/
public function validateAtLeastEmailOrPhone(ExecutionContextInterface $context, $payload)
{
if ($this->getPhone() === null && $this->getEmail() === null) {
$context->buildViolation('validator.visitor.email-or-phone-required-for-all')->addViolation();
}
}
}
I've been able to workaround the problem by adding a property to my VisitorType form that I define with the Callback constraint on the phone value and then check it with a Callback constraint on the email field, but it doesn't seem very "good practice".
If I only try to call the Callback constraint I get the following error message: "Warning: get_class() expects parameter 1 to be object, string given"
Any help is highly appreciated!
Instead of an callback function you could create your own Constraint. Then the Check would be reusable.
I've been using this to check the password on registration against custom rules.
I try to use the 'choices' option into my form :
class FicheInterventionType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('services', EntityType::class, array(
'class' => 'GestionBundle:Service',
'choice_label' => 'nom',
'multiple' => true,
'choices' => $options['services'],
'required' => false))
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'FichesBundle\Entity\FicheIntervention',
'services' => null
));
}
}
controller :
$form = $this->createForm(FicheInterventionType::class, $ficheObservation,
array('services' => $this->getUser()->getCategorie()->getServices()));
but I get all services present in my database, not the services passed with the parameter $option.
I don't know why.
I'm trying to get the result of my choice form.
The form is like this :
$users = $em->getRepository('UserBundle:User')->findAll();
$form = $this->createFormBuilder($users)
->add('users', 'entity', array(
'label' => 'Pick that user',
'class' => 'UserBundle:User',
'choice_label' => 'usFirstname'))
->add('save', 'submit', array('label' => 'Submit'));
Then I want to get the user picked, I try several things, but nothing worked...
It should be something like this :
$user_picked =
$em->getRepository('UserBundle:User')->
findBy(array('usFirstname' => $form->getForm()->get('users')->getData()));
How should I do to get the user picked after the button 'Submit' is clicked ?
The magic of the form builder does this work for you.
If you dump the data dump($form->get('users')->getData()) it will be an instance of the user for the choice in the form. No need to to do an extra query.
Also the choice_label just filters what is in the text of the select option, not the value.
Example;
<?php
namespace AppBundle\Controller;
use AppBundle\Entity\User,
AppBundle\Form\MyFormType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route,
Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter,
Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Request,
Symfony\Bundle\FrameworkBundle\Controller\Controller;
class MyController extends Controller
{
public function addAction(Request $request)
{
$form = $this->createForm(new MyFormType(), null, []);
$form->add('submit', 'submit', [
'label' => 'Create',
'attr' => ['class' => 'btn btn-success pull-right']
]
);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
dump($form->get('users')->getData());
exit(0);
}
return $this->render('AppBundle:user:form.html.twig', [
'form' => $form->createView()
]
);
}
}
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType,
Symfony\Component\Form\FormBuilderInterface,
Symfony\Component\OptionsResolver\OptionsResolver,
Symfony\Component\Validator\Constraints as Assert;
class MyFormType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('users', 'entity', [
'required' => false,
'class' => 'AppBundle:User',
'choice_label' => 'usFirstname',
'expanded' => false,
'multiple' => false,
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => null,
));
}
/**
* #return string
*/
public function getName()
{
return 'my_form';
}
}
I have created a complex symfony form with nested collection. When i create my form with data from orm it works :
$categories = $this->getDoctrine()->getRepository('xx:CategoryNutritionProgram')->findAll();
$form = $this->createForm(new CategoryCollectionFormType(), array('categories' => $categories));
CategoryCollectionFormType :
class CategoryCollectionFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('categories', 'collection', array(
'type' => new CategoryFormType(),
'cascade_validation' => true,
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'prototype_name' => '__i__'
));
}
/**
* Returns the name of this type.
*
* #return string The name of this type
*/
public function getName()
{
return 'simulator_category_collection';
}
}
CategoryFormType :
class CategoryFormType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'hidden');
$builder->add('nutritionPrograms', 'collection', array(
'type' => new NutritionProgramFormType(),
'cascade_validation' => true,
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'prototype_name' => '__j__'
));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'xx\Entity\CategoryNutritionProgram'
));
}
/**
* Returns the name of this type.
*
* #return string The name of this type
*/
public function getName()
{
return 'simulator_category';
}
}
etc ...
But handleRequest() function doesn't work and $data are empty when i submit form. Where is the problem ? my code seems good
$data = array('categories' => array());
$form = $this->createForm(new CategoryCollectionFormType(), $data);
$form->handleRequest($request);
Below the data sent on POST request :
simulator_category_collection[categories][0][name]:cat1
simulator_category_collection[categories][0][nutritionPrograms][0][name]:prog1
simulator_category_collection[categories][0][nutritionPrograms][0][applications][0][name]:app1
simulator_category_collection[categories][0][nutritionPrograms][0][applications][0][product]:1
simulator_category_collection[categories][0][nutritionPrograms][0][applications][0][dose]:5
simulator_category_collection[categories][0][nutritionPrograms][0][applications][0][unit]:5
simulator_category_collection[categories][0][nutritionPrograms][0][applications][1][name]:app2
simulator_category_collection[categories][0][nutritionPrograms][0][applications][1][product]:2
simulator_category_collection[categories][0][nutritionPrograms][0][applications][1][dose]:6
simulator_category_collection[categories][0][nutritionPrograms][0][applications][1][unit]:6
simulator_category_collection[categories][0][nutritionPrograms][1][name]:prog2
simulator_category_collection[categories][0][nutritionPrograms][1][applications][0][name]:app3
simulator_category_collection[categories][0][nutritionPrograms][1][applications][0][product]:3
simulator_category_collection[categories][0][nutritionPrograms][1][applications][0][dose]:7
simulator_category_collection[categories][0][nutritionPrograms][1][applications][0][unit]:7
simulator_category_collection[categories][1][name]:cat2
simulator_category_collection[categories][1][nutritionPrograms][0][name]:prog3
simulator_category_collection[_token]:xxx
Thx for help
Resolved, instead of trying to get data from orginal object like this :
$data = array('categories' => array());
$form = $this->createForm(new CategoryCollectionFormType(), $data);
$form->handleRequest($request);
// here array $data is empty
I have to use $form->getData() because my array is passed by value
I am using a collection with type field type. This is retrieving fields from a table. I am able to add/edit the fields.
What I would like to do is only return certain fields in the collection.
eg only the fields where the column reference_id = 2
The reason I would like to do this is because I am grouping the returned values to their group.
So I will be returning multiple collections grouped by their group.
DetailsType.php
$builder
->add('FieldText', 'collection', array(
'type' => new FieldTextType(),
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
))
FieldTextType.php
$builder
->add('label', 'text')
->add('value', 'text')
;
Edit - Added the type
DetailsType.php
class VillaDetailsType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('CollectionOne', 'collection', array(
'type' => new FieldTextType(),
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
))
->add('CollectionTwo', 'collection', array(
'type' => new FieldCheckboxType(),
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'some/entity'
));
}
/**
* Returns the name of this type.
*
* #return string The name of this type
*/
public function getName()
{
return 'base';
}
FieldTextType.php
class FieldTextType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('label', 'text')
->add('value', 'text')
;
}
/**
* {#inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Some/Entity'
));
}
/**
* Returns the name of this type.
*
* #return string The name of this type
*/
public function getName()
{
return 'detail';
}