Validating a choice field against an array Symfony2 - forms

I am building a form class in Symfony2. In my class, I have a choice field. I built a function to return my choice array:
public function getCardTypes() {
return array('visa' => 'Visa', 'mc' => 'MasterCard', 'amex' => 'American Express');
}
Later, I add a choice field to my form with this array:
$builder->add('PaymentCCType', 'choice', array('choices' => $this->getCardTypes()));
And then in getDefaultOptions function I have a choice constraint for this field:
'PaymentCCType' => new Choice(array('choices' => $this->getCardTypes())),
I seem to be having a problem with this validator. When I submit this form, I get the following error underneath my select box: "The value you selected is not a valid choice". Of course, I am using one of the choices in my array.
What am I doing wrong?
/* edit */
I have noticed that of the 4 fields I have like this, I only get the error on 3 of them. the one where the choice is month (simple 1-12), validation works.
/* edit 2 */
the issue appears to occur when the array key does not match the value. i switched my array to array('Visa' => 'Visa', 'MasterCard' => 'MasterCard', 'American Express' => 'American Express') and now it works.
Is there any way around this? I feel like I can't be the only one with this issue. it occurs even when you have a regular (non-associative) array like array('Visa', 'MasterCard', 'American Express')

IMHO you should do it in different way, create class with ChoiceListInterface with methods:
public function getChoices()
{
return self::$choices;
}
public static function getTypeChoicesKeys()
{
return array_keys(self::$choices);
}
in form class:
$builder->add('type', 'choice',
array(
'expanded' => true,
'multiple' => false,
'choice_list' => new TypeChoices(),
'required' => true,
)
)
in validation.yml
type:
- NotNull: ~
- Choice: { callback: [TypeChoices, getTypeChoicesKeys] }

edit
In response to my issue, the Symfony team pointed out the choice validator accepts an array of possible values (not possible choices like the choice field). the easiest way to do this is to use the array_keys function:
'PaymentCCType' => new Choice(array('choices' => array_keys($this->getCardTypes()))),

Related

Check a radio button by default (unmapped ChoiceType field)

I'm trying to have one of the options of a ChoiceType field selected by deafult using Symfony's FormBuilder.
I found a lot of similar questions, but most are about a field mapped to an entity, which is not my case here. The other answers I found involved using the "data" or the "empty_data" attribute, none of which seem to work for me.
Here's my current "add" method (in my FormBuilder):
$builder->add('type2', ChoiceType::class, [
'mapped' => false,
'label' => false,
'choices' => [
"Incoming" => ComEntry::INCOMING,
"Outgoing" => ComEntry::OUTGOING,
],
'expanded' => true,
'empty_data' => 'Incoming',
]);
(I also tried with 'empty_data' => ComEntry::INCOMING,.)
When rendering the form, no radio box is selected, what I would like is to have the first one ("Incoming") selected by default.
Thanks by advance for any reply :-)
EDIT : Here's what I used in the end (instead of 'empty_data' => 'Incoming',), because type2 is a subset of type (type is a sum of bits, type2 is a choice between the INCOMING and the OUTGOING bits).
'choice_attr' => function($choice, $key, $value) use ($options) {
// If the record is being edited and the user selected "Outgoing" at creation, check the "Outgoing" choice.
if($options['data']->getType() & ComEntry::OUTGOING and $choice == ComEntry::OUTGOING)
return ['checked' => 'checked'];
// Else, check the "Incoming" choice.
elseif($choice == ComEntry::INCOMING)
return ['checked' => 'checked'];
else
return [];
},```
You can try using the choice_attr option in its callable form:
This can be an associative array where the keys match the choice keys
and the values are the attributes for each choice, a callable or a
property path.
'choice_attr' => function($choice, $key, $value) use ($options) {
// If no value is set mark INCOMING as checked
if (empty($options['data']->type2) && ComEntry::INCOMING == $value) {
return ['checked' => 'checked'];
}
return [];
},
What you can do is : 'data' => ComEntry::INCOMING instead of the empty_data
The empty_data is useful when the form is submitted.
But this method will work only for creation. For the edition you will have to check if the form has been initialized with data.

Symfony: how to place a specific item in first position with query_builder option?

In a form, I use an EntityType field, which allows selection of several items from entity Member. I am sending the id of a specific member to my form through the form's option (a variable named $selfId) and would like to use the query_builder function to return a list of members where this specific member would appear in first position. How could I achieve this? I'm using Symfony 3.
I'm thinking of something like this:
->add('members', EntityType::class, array(
'required' => true,
'label' => 'Members',
'class' => 'AppBundle:Member',
'multiple' => true,
'query_builder' => function (MemberRepository $er) use ($selfId) {
$qb = $er->createQueryBuilder('m');
return $qb
->orderBy('m.id = :selfId') // invented code!!!!!!!
->setParameter('selfId', $selfId)
;
}
))
;
There is an item you can add to the code above item called preferred_choices that should do what you are asking
http://symfony.com/doc/current/reference/forms/types/entity.html#preferred-choices

Symfony2 form type nested too many queries

I am developing a webshop system and currently I am working at the admin tools. I've got 4 related entities
Articles
stores main article data (name, description)
ArticleSuppliers
stores variants data (articleNumber, price..)
ArticleAttributesValues
stores attributes for each variant (value e.g. red, 40cm)
ArticleAttributes
stores names of attributes (color, height...)
Since it's much easier to edit a product, I would like to merge the forms together which is working.
ArticlesType binds ArticleSuppliersType binds ArticleAttributesValuesType
My FormType: ArticleAttributesValues contains an entity choice of ArticleAttributes
This is working! But there is a huge problem. I display each variant with their attributes so there is a query for each attribute (imagine a product with 20 variants and 10 attributes).
The solution would be easy: I just need to give an array of attributeNames + id to my FormType, but I do not know how this is done.
I would be grateful for every other solution though.
Thank you in advance!
EDIT:
I will try to explain my problem with code:
// controller
$article = $em->getRepository('MyBundle:Articles')->find($id);
$form = $this->createForm(new ArticleType(), $article);
This is my article type:
// articleType
$builder->add('shortName', 'text',
array('label' => false))
->add('shortDescription', 'text',
array('label' => false))
->add('longDescription', 'textarea',
array('label' => false))
->add('variants', 'collection', array('type' => new VariantsType()))
->add('save', 'submit', array('label' => 'Save'));
This relates to VariantsType:
// variantsType
$builder->add('supplierArticleNumber', 'text',
array('label' => false))
->add('price', 'text',
array('label' => false))
->add('variantvalues', 'collection', array('type' => new VariantsvaluesType()));
This relates to VariantsvaluesType, where my choice field is.
// variantsvaluesType
$builder->add('attributeValue', 'text',
array('label' => false))
->add('attributeUnit', 'text',
array('label' => false, 'required' => false))
->add('attrName', 'entity', array(
'class' => 'MyBundle:ArticleAttributes',
'property' => 'attributeName',
));
This choice field is the same (of course there are changes sometimes), so it would be unnecessary to query it X-times...
My idea was to load all attributeNames in my controller and pass it via $options to variantsvaluesType, but this is not working...
I see, well maybe you can try the next idea. Create a service with a get function for each collection to load, then in the constructor of the service you can load all the list only one time. Then you can use that service wherever you needed. It must work like a singleton. The inconvenience is that all those list should be loaded all the time in memory, but nothing is for free. Will be something like this:
use Doctrine\ORM\EntityManager;
class CollectionsService
{
private $em;
private $collectionOne;
private $collectionTwo;
public function __construct(EntityManager $entityManager)
{
$this->em = $entityManager;
$this->collectionOne= $this->em->getRepository('AppBundle:CollectionOne')->findAll();
$this->collectionTwo= $this->em->getRepository('AppBundle:CollectionTwo')->findAll();
}
public function getCollectionOne(){
return $this->collectionOne;
}
public function getCollectionTwo(){
return $this->collectionTwo;
}
}
Also must work something in the functions like next one , and don't be necessary do the load in the constructor.
public function getCollectionOne(){
if($this->collectionOne == null){
$this->collectionOne= $this->em->getRepository('AppBundle:CollectionOne')->findAll();
}
return $this->collectionOne;
}
Then expose the class as a service in services.yml
parameters:
collection.controller: AppBundle\Services\CollectionsService
services:
collections.service:
class: "%collection.controller%"
arguments:
entityManager: "#doctrine.orm.entity_manager"
And finally just use the service in the controller or the form to update the data $options['data'].
$collectionOne = $this->get('collections.service')->getCollectionOne();
I hope this help you.

symfony2 form collection with an empty row

I have a form with an collection:
$builder->add('languages', 'collection', array(
'type' => new LanguageType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
));
In the twig-template I render an additional row with an empty (LanguageType). For that I use the prototype and replace the name.
In the Controller I bind the Request and discard the "empty" Language in the setter:
public function addLanguage($lang)
{
if($lang->getLanguage())
{
$this->languages->add($lang);
}
// discard the others
}
For some Reason the "empty" row throws an validation error via the NotBlank validator.
My only working solution is to remove the empty row from the raw-Request but thats ugly.
Your solution is valid.
Otherwise you could set the option required to false on $builder->add('lang') but then you would have to do some extra checking yourself.
$builder->add('lang', 'text', array(
'required' => false
));
Documentation about the required option.
Or you can create some JS code that injects the name of the input only when the field is not empty (input with an empty name doesn't have its value sent through). It isn't great either but it works.

Translate select options in Symfony2 class forms

I'm using a class form in Symfony2 Beta3 as follows:
namespace Partners\FrontendBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class ConfigForm extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('no_containers', 'choice', array('choices' => array(1 => 'yes', 0 => 'no')));
...
I want to translate the 'yes' and 'no' options, but I don't know how to use the translator here.
You can use the translation resources as usual. This worked for me:
$builder->add('sex', 'choice', array(
'choices' => array(
1 => 'profile.show.sex.male',
2 => 'profile.show.sex.female',
),
'required' => false,
'label' => 'profile.show.sex.label',
'translation_domain' => 'AcmeUserBundle'
));
And then add your translations to the Resources->translations directory of your Bundle.
Update from #CptSadface:
In symfony 2.7, using the choice_label argument, you can specify the translation domain like this:
'choice_label' => 'typeName',
'choice_translation_domain' => 'messages',
Without specifying the domain, options are not translated.
I searched a while to find an answer, but finally I found out how Symfony translates form content. The easiest way in your case seems to be to just add a translation for "yes" and "no" by adding a YAML or XLIFF translation file to your application (e.g. app/Resources/translations/messages.de.yml) or your bundle. This is described here:
http://symfony.com/doc/current/book/translation.html
The problem - in my opinion - is that you don't seem to be able to use custom translation keys. The guys from FOSUserBundle solve this (or a similar) problem with "Form Themes" (http://symfony.com/doc/2.0/cookbook/form/form_customization.html). Here are two significant lines of code to achieve the usage of the form element id as translation key:
https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/views/Registration/register_content.html.twig#L1 / https://github.com/FriendsOfSymfony/FOSUserBundle/blob/50ab4d8fdfd324c1e722cb982e685abdc111be0b/Resources/views/form.html.twig#L4
By adding a Form Theme you're able to modify pretty much everything of the forms in the templates - this seems to be the right way of doing this.
(Sorry, I had to split two of the links b/c I don't have enough reputation to post more than two links. Sad.)
In symfony 2.7, using the choice_label argument, you can specify the translation domain like this:
'choice_label' => 'typeName',
'choice_translation_domain' => 'messages',
Without specifying the domain, options are not translated.
CptSadface's answer was what helped me with translating my entity choices.
$builder
->add(
'authorizationRoles',
null,
[
'label' => 'app.user.fields.authorization_roles',
'multiple' => true,
'choice_label' => 'name', // entity field storing your translation key
'choice_translation_domain' => 'messages',
]
);