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.
Related
I'm building a form which has an EntityType element, the problem I have is that I'm unable to have the placeholder translated.
Here is my code:
$builder
->add('Products', EntityType::class, [
'mapped' => false,
'expanded' => true,
'multiple' => false,
'required' => false,
'class' => Product::class,
'choices' => $options['step']->getProducts(),
'placeholder' => 'form.mat.alreadyOwned',
'label' => 'form.mat.alreadyOwned',
'translation_domain' => 'messages'
])
When I use the form.mat.alreadyOwnedtranslation as the label it works fine, but I would like to use it in the placeholder instead. What am I missing?
Looking forward to any tips or tricks that you have to offer!
[UPDATE]
As pointed out by #gp_sflover I'm not trying to replace the general placeholder, but the one for the empty value. This one only appears if you set required to false.
After some research and thought, the places where the placeholder is actually used are quite limited in number (as it should be). However, specific placeholder translation is not a special case (sadly).
For every choice in a ChoiceType a ChoiceView is added. Also for the placeholder a ChoiceView is added, that inherits the options of the form (which is a somewhat sensible choice for the ChoiceType), including the translation_domain parameter, which indicates that the choices shall be translated (all of them). There's also a reference in some twig template that specifically manages the translation in the twig bridge for non-expanded choice types. However, those don't open up a specific best practices answer on how to specifically handle translations for the placeholder in the ChoiceType.
For the EntityType, this doesn't change.
So there are a few approaches, some of which might be absolutely utility-free ...
translate the placeholder right there when building the form
Although this is conceptually not the most beautiful option, IMHO it's still the most practical one. Essentially, in Symfony 4 forms can receive dependencies in their constructor via auto-wiring, so injecting a TranslatorInterface will open up the ability to translate the placeholder with the requests locale (which is automatically set for the translator via event listener):
public function __construct(TranslatorInterface $translator) {
$this->translator = $translator;
}
and in your buildForm you can then use it to translate the placeholder
$builder
->add('Products', EntityType::class, [
'mapped' => false,
'expanded' => true,
'multiple' => false,
'required' => false,
'class' => Product::class,
'choices' => $options['step']->getProducts(),
'placeholder' => $this->translator->trans('form.mat.alreadyOwned'), // <-- change
'label' => 'form.mat.alreadyOwned',
'translation_domain' => 'messages'
])
not to withhold the other options, which in my opinion are almost all overkill...
set a choice_translation_domain for all entries, including placeholder
obviously, this can and probably will lead to problems, if there ever is an entity that whose choice label matches the key in the translator ... and it is not intended for translation. but it will translate the placeholder with the very same translation_domain ...
adapt the form rendering and check for the placeholder
this is problematic, since you have to assign the form theme/form rendering to all relevant forms. the placeholder does have a unique name ('placeholder', who would have thought) so it could very well be used to achieve that goal. However, setting a different translation domain could very much be challenging. (if you attempt this, it's a bit of a nuisance)
write your own entity type (optionally adding own form rendering)
theoretically, you could create your own EntityType and handle the placeholder properly there. like ... adding a translation domain to the choice view and sub form and what not. For inspiration/reference consult the ChoiceType, EntityType and DoctrineType (parent type).
On a form I add a field like this
$builder->add('cse',
ChoiceType::class,
array(
'label' => '',
'required' => true,
'translation_domain' => 'messages',
'choices' => array(
'I agree' => true
),
'expanded' => true,
'multiple' => true,
'data' => null,
'attr' => array( 'class' => 'form_f' ),
)
)
While all other fields added to the form that have 'required' set to 'true' will prevent the form from being send the required attribute for this field is ignored (the form is sent anyways no matter if checked or not).
Do I have to handle this with Assert statements? If yes - still: why is required not working here?
Yes, use Assert.
Because multiple=true print checkbox. Html validator can test radio, but no checkbox.
Always use Assert for all forms, because html validator isn't safe :)
In my case, I cannot use the asserts, and since was not possible to handle this in user side (unless you use javascript), I made the checks in server side, in the FormEvents::PRE_SUBMIT hook:
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) {
if ($field->required && !isset($event->getData()[$field->name])) {
$event->getForm()->addError(new FormError('One option must be chosen on "' . $field->label . '"'));
}
});
I am using FOSRestBundle and standard Symfony2 form functionalities to build a REST API.
I have a form definition like this:
$builder->add('paid', 'checkbox', array(
'required' => false,
))
By sending data over the REST API in JSON format with a PATCH request I can successfully switch the data between true and false:
{
"paid": true
}
respectively
{
"paid": false
}
However, if the data is sent as application/x-www-form-urlencoded then I can only set the data to true, not to false.
This works: paid=1 - a record with paid=false is set to paid=true.
This is ignored: paid=0 - a record with paid=true remains paid=true.
I already considered using a choice type instead of checkbox (which worked in a test run), but this seems not to be the proper way to go.
What could I try to make it work correctly?
you have o use callback to verify if checkbox is not checked and set it to false. I think you have a default value as true. Can you post you entity?
use ChoiceType instead of Checkbox
->add('notificationNews', ChoiceType::class, array(
'choices' => array(
'0' => false,
'1' => true,
'null' => null,
),
'required' => false,
))
I'm trying to implement a ManyToMany relation in a form between 2 entities (say, Product and Category to make simpe) and use the method described in the docs with prototype and javascript (http://symfony.com/doc/current/cookbook/form/form_collections.html).
Here is the line from ProductType that create the category collection :
$builder->add('categories', 'collection', array(
'type' => 'entity',
'options' => array(
'class' => 'AppBundle:Category',
'property'=>'name',
'empty_value' => 'Select a category',
'required' => false),
'allow_add' => true,
'allow_delete' => true,
));
When I had a new item, a new select appear set to the empty value 'Select a category'. The problem is that if I don't change the empty value, it is sent to the server and after a $form->bind() my Product object get some null values in the $category ArrayCollection.
I first though to test the value in the setter in Product entity, and add 'by_reference'=>false in the ProductType, but in this case I get an exception stating that null is not an instance of Category.
How can I make sure the empty values are ignored ?
Citing the documentation on 'delete_empty':
If you want to explicitly remove entirely empty collection entries from your form you have to set this option to true
$builder->add('categories', 'collection', array(
'type' => 'entity',
'options' => array(
'class' => 'AppBundle:Category',
'property'=>'name',
'empty_value' => 'Select a category'),
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true
));
Since you use embedded forms, you could run in some issues such as Warning: spl_object_hash() expects parameter 1 to be object, null given when passing empty collections.
Removing required=>false as explained on this answer did not work for me.
A similar issue is referenced here on github and resolved by the PR 9773
I finally found a way to handle that with Event listeners.
This discussion give the meaning of all FormEvents.
In this case, PRE_BIND (replaced by PRE_SUBMIT in 2.1 and later) will allow us to modify the data before it is bind to the Entity.
Looking at the implementation of Form in Symfony source is the only source of information I found on how to use those Events. For PRE_BIND, we see that the form data will be updated by the event data, so we can alter it with $event->setData(...). The following snippet will loop through the data, unset all null values and set it back.
$builder->addEventListener(FormEvents::PRE_BIND, function(FormEvent $event){
$data = $event->getData();
if(isset($data["categories"])) {
foreach($data as $key=>$value) {
if(!isset($value) || $value == "")
unset($data[$key]);
}
$event->setData($data);
});
Hope this can help others !
Since Symfony 3.4 you can pass a closure to delete_empty:
$builder
->add('authors', CollectionType::class, [
'delete_empty' => function ($author) {
return empty($author['firstName']);
},
]);
https://github.com/symfony/symfony/commit/c0d99d13c023f9a5c87338581c2a4a674b78f85f
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()))),