How to create two related radiobuttons? - symfony

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.

Related

How to hide a boolean type for the deleted property in my form

I have a Symfony 4 entity that includes a boolean property for "deleted". In my repository, I'm only returning entities that aren't marked as deleted. When a user edits an entity and the form displays, there is a checkbox for "deleted" that I'd like to hide.
I tried doing this by changing the field type to HiddenType like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('logo')
->add('deleted', HiddenType::class)
->add('submit', SubmitType::class, [
'label' => 'Create',
'attr' => ['class' => 'btn btn-default pull-right'],
]);
}
But when I submit the form, I get this error:
Expected argument of type "boolean", "NULL" given.
Any ideas on how to properly hide this field the symfony way?
I don't know why you want to include this hidden field in you form, but the fields are, by default, required.
See here :
https://symfony.com/doc/current/reference/forms/types/form.html#required
If you set required to false, this may solve the issue.
If there's no value for deleted or it's set to null, you can set it by default to false like this :
$builder->add('deleted', HiddenType::class, [
'empty_data' => false
]);

Make Symfony forms use simple GET names

I am currenly creating a simple search form, using Symfony 4.1's form builder. Rendering works great, but I lack a bit of control on the way the request is formed.
The generated request looks like:
http://localhost:8000/fr/search?advanced_search%5Bquery%5D=test&advanced_search%5Bcategory%5D=1&advanced_search%5BverifiedOnly%5D=&advanced_search%5Bsave%5D=
And I want it to look like:
http://localhost:8000/fr/search?query=test&category=1&verifiedOnly=
The code I'm using:
$this->ff->create(
AdvancedSearchType::class,
null,
['csrf_protection' => false]
)->createView()
And
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setMethod('GET')
->add('query', SearchType::class,
[
'attr'=> [
'placeholder' => 'search.query'
]
]
)
->add('category', EntityType::class,
[
'class' => Category::class,
'label' => 'search.category',
'group_by' => function(Category $category, $key, $value) {
// Code...
return $category->getName();
}
]
)
->add('verifiedOnly', CheckboxType::class,
[
'label' => 'search.verifiedOnly',
'required' => false
]
)
->add('save', SubmitType::class,
[
'label' => 'search.submitButton'
]
);
}
The reason behind this is because it generates widgets like this
<input .. name="advanced_search[query]" ..>
When I want them like this
<input .. name="query" ..>
Is there any way to change this? Thank you!
When you are using the shortcut method provided by Symfony's ControllerTrait, the name of the generated form will be automatically derived from the form type's block prefix.
You can change the implicit name for all forms based on this type by overriding the getBlockPrefix() method in your form type:
public function getBlockPrefix()
{
return '';
}
Or you decide to change the name for just one particular form by giving its name explicitly making use of the form factory that is used under the hood by the controller trait's shortcut methods like this:
$form = $this->get('form.factory')
->createNamedBuilder('', AdvancedSearchType::class, null, [
'csrf_protection' => false,
])
->getForm();
But you need to be careful now. If there is more than one form without a name handled during the same request, the component is not able to decide which of these forms has been submitted and will act as if both had been so.

doctrine : ManyToOne : the field is inserted in the DB with NULL value [duplicate]

I'm searching how to implement a system of choosing languages dynamically using form builder.
so we must have two html array inputs field : name="languages[]" and the second will be name="languges_level[]"
So this system allows the user to set the language which he can speak with his level on it.
The User can add/remove Language dynamically before he submits the Form.
The Questions :
1- Form Level : what will be the field form type? I have to add 2 form fields which will be combined to create my result array which will be stored in the database. So this two field will be not mapped with ORM.
->add('langues', TYPEXXX:class)
->add('langues_level', TYPEXXX:class)
3- Twig Level: should i make some change in the twig as well?
So what will be the best solution in my case?
My first try is :
->add('languages', CollectionType::class, array(
'entry_type' => ChoiceType::class,
'entry_options' => array(
'choices' => array(
'Français' => 'Français',
'English' => 'English',
'Italien' => 'Italien',
'Espanish' => 'Espanish',
),
'label' => ' ',
),
))
->add('language_levels', CollectionType::class, array(
'entry_type' => ChoiceType::class,
'entry_options' => array(
'choices' => array(
'Beginner' => 'Beginner',
'Medium' => 'Medium',
'Good' => 'Good',
),
'label' => ' ',
),
));
but this don't work as I mentioned in the picture .. who had a perfect solution plz ?
I think you need a Collection of Forms.
so we must have two html array inputs field : name="langues[]" and the
second will be name="langes_level[]"
(..)
3- Twig Level: should i make some change in the twig as well?
(..)
4- Javascript Level, i can develop it when the inputs are clean
created in the html.
No, no and no. Describing how your 'array input fields' should be named exactly is not the right mentality if you're using a framework like Symfony. Describe your entity fields, describe your form fields and Symfony will give all form elements a name. Symfony Forms will render and handle the form for you, so there is (very likely) no need to be bothered what the form element names are exactly.
Your entity class:
class LanguageLevel
{
protected $user;
protected $language;
protected $level;
//getters and setters
}
Create a form type:
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('languages', CollectionType::class, array(
'entry_type' => LanguageLevelType::class,
'allow_add' => true,
));
}
}
And a LanguageLevelType:
class LanguageLevelType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('language', LanguageType::class)
->add('level', ChoiceType::class, array(
'choices' => array(
'Good' => 5,
'Bad' => 1,
),
));
}
}
If the rendered output is not what you want, check the documentation if you can configure the Form Types. Manually changing the twig template, your controller and/or javascripts for a specific case is possible, but I think the 'Collection of Forms' from above will cover your use case.

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

Use sonata_type_collection inside custom type

What I want to do is add sonata_type_collection to my custom formType.
Normal way is add sonata_collection_type to $formMaper inside AdminClass like:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper->add('elements, 'sonata_type_collection', array(
'some_options' => 'options'
))
}
It work perfect, but i have my custom form type, and when i defined it like:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$formMapper->add('elements, 'sonata_type_collection', array(
'some_options' => 'options'
))
}
It doesn't work (it appear only label of filed). Problem is wrong template, so I tried to set formAdminTemplate
I made it by set template in view
{% form_theme formElement 'SonataDoctrineORMAdminBundle:Form:form_admin_fields.html.twig' %}
Problem is sonata_admin variable inside this 'formTheme'. This variable doesn't exist in my form.
Of course my form type is related to admin class but i don't know how could I I tell symfony about this relation
You need an admin class for your collection child :
$formMapper->add('customizations', 'sonata_type_collection',
array(
'required' => true,
'type_options' => array('delete' => true),
'by_reference' => false,
'mapped' => true
),
array(
'edit' => 'inline',
'inline' => 'table',
'sortable' => 'position',
'targetEntity' => '/path/to/Entity/Customization',
'admin_code' => 'my.service.customization_admin'
)
);
I find solution. Instead using my custom type, I defined form using admin class. I need this form outside admin so it was little difficult.
First of all in my controller i get admin class from service. Inside admin class I override 3 methods which are use to create form
public function getFormBuilder()
public function defineFormBuilder(FormBuilder $formBuilder)
public function buildForm()
then i had to save my entity by sonata admin way. using create method instead handleRequest.

Resources