Form of CollectionType implementing EntityType in ManyToMany relation: Duplicate record is added to database table when 'multiple' => false - symfony

I have 2 classes with many to many relationship: Foo (owning side) and Bar (inverse side). My form is CollectionType (of Foo) implementing EntityType (of Bar) (add Bar into Foo). This is some code for my form
FooType:
->add('Bars', CollectionType::class, array('entry_type' => BarType::class, 'allow_add' => true, 'allow_delete' => true,
'by_reference' => false))
BarType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('text', EntityType::class,
array(
'class' => Bar::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('bar')
->orderBy('bar.text', 'ASC');
},
'choice_label' => 'text',
'by_reference' => false,
)
)
;
}
if i set 'multiple' => true, there is no problem
if i set 'multiple' => false (dont ask me why, this is a requirement from work: add a plus and minus button for the form. When plus button is click, the options from Bar.text are shown, and user can select select only one from those options. When minus button is clicked, undo the plus action), relationships are established but a duplicate record of selected option is added to Bar table.
I would like to ask:
Why does it happen? Is that the way symfony supposed to works?
Is there any fix/workaround for it?
According to this github disccussion of Symfony, you are supposed to use 'multiple' => true to make it work
I am using Symfony 4.4, doctrine bundle 2.6.3
Edit:
I would like to clarify this: my goal is to select multible options loaded from Bar->$text. But instead of selecting multiple options in one run (by setting multiple true), i would like to archieve it by adding many times (with a plus button), and each time only one option can be selected.
The reason I am doing this is that i am migrating from many to one to many to many, and I do not want to change the current UI (select one at a time) to multiple select.

Related

Symfony 4 - Difficults with ChoiceType function

I've a Symfony 4 project.
My users can submit holidays.
There are several types of leave (illness, overtime, etc.), and for each of these types, they have a balance.
When they submit a leave, they must choose from the list of leave types the type they wish. But I want to display in this list only those whose balance is greater than 0.
So, in my form I've:
->add('typeConge', EntityType::class, [
'class' => TypeConge::class,
'label' => false,
'placeholder' => "Type d'absence",
'choice_label' => 'nom',
])
And in my User entity, I've the function to take only typeConges with positive balance:
public function getSoldesActif()
{
$soldesTypesConge = $this->soldeConges;
foreach ($soldesTypesConge as $solde) {
if ($solde->getSolde() == 0) {
$soldesTypesConge->removeElement($solde);
}
}
return $soldesTypesConge;
}
But even with the documentation, I don't understand how can I affect this list ?
Can someone help me please ?
You can use the query_builder-option on the entity type to specify which entities are displayed.
It should probably look roughly, something like this:
->add('typeConge', EntityType::class, [
'class' => TypeConge::class,
'label' => false,
'placeholder' => "Type d'absence",
'choice_label' => 'nom',
'query_builder' => function(EntityRepository $repo) {
$builder = $repo->createQueryBuilder('typeConge');
return $builder->where('typeConge.solde > 0');
},
])
The query builder is responsible for which entities are fetched and then displayed in the list. By default it will fetch all entities and with the query_builder-option you can restrict this to your liking using the ORM-query builder from Doctrine.
There is also an example in the docs, you can use as reference: https://symfony.com/doc/current/reference/forms/types/entity.html#ref-form-entity-query-builder

How to create two related radiobuttons?

The code below creates 2 radiobuttons, however they are not related to each other. One is rendered with a name description_form[friend] and the other one with the name - description_form[guide]. How can they be rendered with the same name? The documentation is not clear about this subject.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('friend', RadioType::class, array(
'label' => 'Friend',
'required' => false
))
->add('guide', RadioType::class, array(
'label' => 'Guide',
'required' => false
));
}
Using a list of RadioType is not quite easy, that's why everybody recommends you to use a ChoiceType which dynamically creates a radio list depending on an array of choice data.
When you create a FormTypeInterface, it has to represent (commonly) one field or one sub form in a global form, so each field name has to be unique to be mapped to the corresponding data.
The buildForm method allows to add some sub fields in you FormType, in your case the field holds two sub fields as radio button and each has a specific name, this is intended by default, but you should always keep in mind the global array data you want to deal with.
Here's your example :
class MyCustomFormType extends \Symfony\Component\Form\AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('friend', RadioType::class, array(
'label' => 'Friend',
'required' => false
))
->add('guide', RadioType::class, array(
'label' => 'Guide',
'required' => false
));
}
public function getBlockPrefix
{
return 'my_custom';
}
// ...
}
So this form type data should look like :
$myCustomFormData = array(
'friend' => $friendData,
'guide' => $guideData,
);
And nested in a global form it would be :
$formData = array(
'my_custom' => $myCustomFormData,
);
But you can name the field as you want :
// In a controller extending \Symfony\Bundle\FrameworkBundle\Controller\Controller
$form = $this->createFormBuilder()
->add('custom_field', MyCustomFormType::class)
->getForm();
// Then
$form->setData(array('custom_field' => $myCustomFormData));
Note that currently, since you map "friend" and "guide" data to RadioType they should hold a boolean value as :
$myCustomFormData = array(
'friend' => true, // checked
'guide' => false, // unchecked
);
But how would you unselect a value then ?
You should had a placeholder to do that, and handle it while submission...
Also, changing the name can be done using the finishView method of your type class, it takes the FormView (built view of your type), the form itself and the options as arguments :
public function finishView(FormView $view, FormInterface $form, array $options)
{
$childName = $view->vars['full_name']; // "my_custom" by default
foreach ($view as $childView) {
$childView->vars['full_name'] = $childName;
}
}
But you would also need to add a DataMapperInterface to get back the submitted value to the form type itself instead.
To do all that, you need to know how the Form Component works and it's not easy.
Easy way
So I agree with the other answers, you should use a ChoiceType to get it out-of-the-box.
I assume your custom form type is about choosing either a "friend" or a "guide", so it could look like this :
$builder
->add('fellow', ChoiceType::class, array(
'choices' => array(
'I have a friend' => 'friend',
'I\'d like a guide' => 'guide',
),
'expanded' => true, // use a radio list instead of a select input
// ...
Have a look at the official docs
Then your data will look like :
$form->add('custom_field', MyCustomFormType::class);
$form->setData(array(
'custom_field' => 'friend',
));
When rendered the "friend" choice will be selected and you will be able to change it to "guide".
An array of choices for the choices options takes labels as keys and choice values as values :
<div id="form_custom_field">
<input type="radio" name="form[custom_field]" value="friend" checked="checked">
<label>I have a friend</label>
<input type="radio" name="form[custom_field]" value="guide">
<label>I'd like a guide</label>
...
This is how I do radio buttons in Symfony 2.7 , hope it helps you.
$yes_no = array('1'=>'Yes','0'=>'No');
->add('myfieldname', 'choice',array(
'choices' => $yes_no,
'label'=>'YourLabelGoeshere',
'required'=>true,
'expanded'=>true,
'multiple'=>false,
'placeholder'=>false
))
Perhaps consider using the ChoiceType field.
See here: Documentation
This allows you to output the options as radio buttons if you choose.
RadioType is used internally by ChoiceType. In most use cases you want to use ChoiceType.

Symfony: how to add a sub set of items from big collection to entity type?

A car machine oil is applicable to several car models, but in my case, there are hundreds of car models in our system, I don't want to load them all to a page, then , let a user to choose specific car models, it would be better to use ajax call to get car model by car brand , car series. the user choose a sub collection of items , post these selected to server.
In the form type ,I add a form field like this below, as we know, if I don't set its option choices as empty array, the entity field will get all car models , which will result in huge performance penalty.
->add('applicableModels', 'entity', array(
'class' => 'VMSP\CarBundle\Entity\CarModel',
'choices'=>array(),
'multiple'=>true,
'property_path' => "modelName",
'label' => 'vmsp_product.product.form.applicable_model',
)
)
So, how do you add a sub set of items from a big collection , and assign these selected items to entity type?
The simple answer is that you can define a custom query builder for each element, you add to the form, i.e.
$data = $builder->getData();
\\...
->add('applicableModels', 'entity', array(
'class' => 'VMSP\CarBundle\Entity\CarModel',
'multiple' => true,
'property_path' => "modelName",
'label' => 'vmsp_product.product.form.applicable_model',
'query_builder' => function (EntityRepository $er) use ($data) {
$qb = $er->createQueryBuilder('e')
->where('e.manufacturer IN (:manufacturer)')
->setParameter('manufacturer', $data['manufacturer']);
return $qb->orderBy('e.name');
},
))
So you may have a sort of a "wizard" where user select a manufacturer, the page reloads, and the cars shown are only a subset.
In a very similar situation I ended up doing things a little differently, though. In the form, the choices are set to an empty array. On frontend, the options are filled via Ajax calling a separate API. Then on the form "pre-submit" event, I fill the field with the selected option.
$builder->addEventListener(
FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
if (!empty($data['applicable_model'])) {
$form->add('applicable_model', 'entity', array(
'class' => 'VMSP\CarBundle\Entity\CarModel',
'query_builder' => function (EntityRepository $er) use ($data) {
$qb = $er->createQueryBuilder('e')
->where('e.id IN (:applicable_model)')
->setParameter('applicable_model', $data['applicable_model']);
return $qb;
},
));
}
Update: I learned that the addEventListener part can probably be replaced by a DataTransforme instead, see https://stackoverflow.com/a/31938905/410761

symfony2 FromBuilder multiple checkboxes from entity instead of using "choice"

How can I get multiple Cehckboxes instead a choice field in Symfony? Actually, I'm using:
->add('usergroups', 'entity', array('class' => 'PrUserBundle:Group','property' => 'name','required' => true))
This will output a select-Field..
In this case, it would be better to output checkboxes as it is easier to handle for the user.
http://symfony.com/doc/current/reference/forms/types/checkbox.html at, there is nothing helpful about multiple select....
Do I have to build an array and place it by myself?
Use the expanded option for choice and entity fields.
->add('usergroups', 'entity', array('class' => 'PrUserBundle:Group','property' => 'name','required' => true, 'expanded' => true,))

How to validate two instances of the same entity?

Use case
I am learning Symfony2 and am creating a Table Tennis tracking app to learn the framework. I have configured my entities as follows.
Player 1..n Result n..1 Match
On my form I'd like to validate that the scores for a match are correct.
Implementation
Match has an ArrayCollection() of results.
My MatchType and ResultType forms contain the following.
// Form\MatchType
$builder->add('matchType', 'entity', array(
'class' => 'PingPongMatchesBundle:MatchType',
'property' => 'name',
)
)
->add('results', 'collection', array(
'type' => new ResultType(),
'allow_add' => true,
'by_reference' => false,
)
)
->add('notes');
// Form\ResultType
$builder->add('player', 'entity', array(
'class' => 'PingPongPlayerBundle:Player',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('p')
->orderBy('p.firstName', 'ASC');
},
))
->add('score');
Issue
I need to be able to validate the scores. However I'm not sure how to approach this type of validation as I need to compare two instances of my Result#score in order to know if they are valid or not.
Is anyone able to suggest a method or approach that I could use in order to be able to compare Result#score between the two different instances? Can I validate the ArrayCollection in the Match entity for example?
You could creat a custom validator constraint on Match entity.
http://symfony.com/doc/2.0/cookbook/validation/custom_constraint.html
Take a look in Callback constraint:
http://symfony.com/doc/2.1/reference/constraints/Callback.html

Resources