Trying to validate a choice field (multiple checkboxes) Im having this problem:
"Notice: Array to string conversion "
My validation file looks like this one:
Cgboard\AppBundle\Forms\UploadImageEntity:
properties:
image:
...
cgnetworks:
- Choice:
choices: [flickr, tumblr] //<--- this is giving me problems!!!
My form entity class (Im not going to save this to db for now):
class UploadImageEntity {
public $image;
public $cgnetworks;
}
And my form class:
class UploadImageForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('image', 'file')
->add('cgnetworks', 'choice', [
'choices' => $this->getCgNetworks(),
'multiple' => TRUE,
'expanded' => TRUE
]
);
}
public function getCgNetworks()
{
return [
'tumblr' => 'Tumblr',
'flickr' => 'Flickr'
];
}
}
Any idea?
perhaps you need to specify multiple in your validation
cgnetworks:
- Choice:
choices: [flickr, tumblr] //<--- this is giving me problems!!!
multiple: true
Check your Entity field getter. If you have something else instead of
public function getValue(){
return $this->value;
}
You can reach this error.
Form builder uses get and set entity methods, that's why you need to return an allowable value.
Related
We have a simple form:
namespace App\Form;
...
class SimpleForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('field_1', TextType::class, [
'required' => true,
'mapped' => false,
'constraints' => [
new NotBlank()
]
])
->add('field_2', TextType::class, [
'required' => true,
'mapped' => false,
'constraints' => [
new NotBlank()
]
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'constraints' => [
new CustomCheck()
]
]);
}
}
If my understanding is right, CustomCheck() can refer to a complex validation over the whole form data (for instance, to validate some combinations of inputs).
My next step is to create the App\Validator\CustomCheck and App\Validator\CustomCheckValidator classes, as per Symfony's manual.
However, I do not know how to pass the submitted field_1 and field_2 data to "new CustomCheck()". Or, how to access all submitted fields from within my custom validator.
I found it is possible if I were using an Entity (Class Constraint Validator, https://symfony.com/doc/current/validation/custom_constraint.html#class-constraint-validator). But I want to know if it's possible without using an Entity.
Okay, so my findings on the matter is that there is no programmatically way to access and pass the form unmapped fields data as arguments at the level of CustomCheck() within:
$resolver->setDefaults([
'constraints' => [
new CustomCheck()
]
]);
In my case, with no mapped Entity and no mapped fields, I found two ways to have a custom validator that can access any form field data:
A custom in-form callback validator:
// custom callback validator
public function CustomCheck($data, ExecutionContextInterface $context){
// $data doesn't contain the unmapped fields, so I need to extract the form data differently
//var_dump($data['field_1']); // this works only for mapped fields (no Entity/DTO needed for this to work, only mapped fields is sufficient)
$field1_data = $context->getRoot()->get('field_1')->getData(); // this works
$field2_data = $context->getRoot()->get('field_2')->getData();
if(...something_not_good...) {
$context
->buildViolation('Custom error here')
->addViolation();
}
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'constraints' => [
new Callback([$this, 'CustomCheck'])
]
]);
}
A custom validator where form data needs to be extracted with $this->context:
// form builder
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'constraints' => [
new CustomCheck()
]
]);
}
// CustomCheck constraint
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
class CustomCheck extends Constraint
{
public string $message = 'Invalid blah blah.';
}
// CustomCheck validator
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class CustomCheckValidator extends ConstraintValidator
{
// $value will always be null, because nothing gets passed in the first argument to this custom validator (no mapped entity, no mapped fields)
/**
* #param mixed $value
*/
public function validate($value, Constraint $constraint)
{
// extract unmapped form fields data manually
$values = [
'field_1' => $this->context->getRoot()->get('field_1')->getData(),
'field_2' => $this->context->getRoot()->get('field_2')->getData()
];
if(...something_not_good...) {
$this->context->buildViolation('Custom error here')->addViolation();
}
}
}
We are using Symfony Forms for our API to validate request data. At the moment we are facing a problem with the CollectionType which is converting the supplied value null to an empty array [].
As it is important for me to differentiate between the user suppling null or an empty array I would like to disable this behavior.
I already tried to set the 'empty_data' to null - unfortunately without success.
This is how the configuration of my field looks like:
$builder->add(
'subjects',
Type\CollectionType::class,
[
'entry_type' => Type\IntegerType::class,
'entry_options' => [
'label' => 'subjects',
'required' => true,
'empty_data' => null,
],
'required' => false,
'allow_add' => true,
'empty_data' => null,
]
);
The form get's handled like this:
$data = $apiRequest->getData();
$form = $this->formFactory->create($formType, $data, ['csrf_protection' => false, 'allow_extra_fields' => true]);
$form->submit($data);
$formData = $form->getData();
The current behavior is:
Input $data => { 'subjects' => null }
Output $formData => { 'subjects' => [] }
My desired behavior would be:
Input $data => { 'subjects' => null }
Output $formData => { 'subjects' => null }
After several tries I finally found a solution by creating a From Type Extension in combination with a Data Transformer
By creating this form type extension I'm able to extend the default configuration of the CollectionType FormType. This way I can set a custom build ModelTransformer to handle my desired behavior.
This is my Form Type Extension:
class KeepNullFormTypeExtension extends AbstractTypeExtension
{
public static function getExtendedTypes(): iterable
{
return [CollectionType::class];
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder->addModelTransformer(new KeepNullDataTransformer());
}
}
This one needs to be registered with the 'form.type_extension' tag in your service.yml:
PrivateApiBundle\Form\Extensions\KeepNullFormTypeExtension:
class: PrivateApiBundle\Form\Extensions\KeepNullFormTypeExtension
tags: ['form.type_extension']
Please note that you still use the CollectionType in your FormType and not the KeepNullFormTypeExtension as Symfony takes care about the extending...
In the KeepNullFormTypeExtension you can see that I set a custom model transformer with addModelTransformer which is called KeepNullDataTransformer
The KeepNullDataTransformer is responsible for keeping the input null as the output value - it looks like this:
class KeepNullDataTransformer implements DataTransformerInterface
{
protected $initialInputValue = 'unset';
/**
* {#inheritdoc}
*/
public function transform($data)
{
$this->initialInputValue = $data;
return $data;
}
/**
* {#inheritdoc}
*/
public function reverseTransform($data)
{
return ($this->initialInputValue === null) ? null : $data;
}
}
And that's it - this way a supplied input of the type null will stay as null.
More details about this can be found in the linked Symfony documentation:
https://symfony.com/doc/current/form/create_form_type_extension.html
https://symfony.com/doc/2.3/cookbook/form/data_transformers.html
Two simple classes form my app model: Money and Product.
As Money app form being reusable, I've decided to create MoneyType extending AbstractType.
// App\Entity\Product
/**
* #ORM\Embedded(class="Money\Money")
*/
private $price;
// App\Form\ProductType
$builder->add('price', MoneyType::class)
// App\Form\Type\MoneyType
class MoneyType extends AbstractType
{
private $transformer;
public function __construct(MoneyToArrayTransformer $transformer)
{
$this->transformer = $transformer;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('amount', NumberType::class, [
'html5' => true,
'constraints' => [
new NotBlank(),
new PositiveOrZero(),
],
'attr' => [
'min' => '0',
'step' => '0.01',
],
])
->add('currency', ChoiceType::class, [
'choices' => $this->getCurrenciesChoices(),
'constraints' => [
new NotBlank(),
],
]);
$builder->addModelTransformer($this->transformer);
}
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver->setDefaults([
'data_class' => null
]);
}
...
}
Is it possible to guess the field type without specifying it explicitly for obtaining the following code?
// App\Form\ProductType
$builder->add('price')
Any help is welcome. Thank you in advance.
You can implement a custom TypeGuesser that reads the doctrine metadata and checks if the field is an embeddable of the desired type. This is a basic implementation
namespace App\Form\TypeGuesser;
use App\Form\Type\MoneyType;
use Symfony\Component\Form\Guess\Guess;
use Symfony\Component\Form\Guess\TypeGuess;
use Symfony\Component\Form\FormTypeGuesserInterface;
use Doctrine\ORM\EntityManagerInterface;
class MoneyTypeGuesser implements FormTypeGuesserInterface
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function guessType($class, $property)
{
if (!$metadata = $this->em->getClassMetadata($class)) {
return null;
}
if (
isset($metadata->embeddedClasses[$property]) &&
'Money\Money' == $metadata->embeddedClasses[$property]['class']
) {
return new TypeGuess(MoneyType::class, [], Guess::HIGH_CONFIDENCE);
}
}
// Other interface functions ommited for brevity, you can return null
}
You can see all the interface methods that you need to implement here.
The Form TypeGuesser is mostly based on the annotation #var Money\Money and you should be able to build your down guesser for your own types, see https://symfony.com/doc/current/form/type_guesser.html
Also take a look at https://github.com/symfony/symfony/blob/4.3/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php on how to guess the type by doctrine orm specific types.
You could derive your own app specific guesser with those two examples.
I'm developing the Admin Panel for my website, and one part of it is managing users. I'm using FOSUserBundle for those tasks.
I'm using custom template (AdminLTE) for forms, and I cannot add a dropdown to select roles for user when I add a new one.
In UserType.php
$builder->add('roles', 'choice', array(
'label' => 'User Role',
'choices' => $this->roleHelper->getRolesForForm()
));
In WCB\SaleBundle\Helper\RoleHelper.php
...
public function getRolesForForm()
{
$roleList = self::flatArray($this->rolesHierarchy);
$roles = array();
foreach ($roleList as $roleId => $roleName) {
if ($roleId == 'ROLE_ADMIN') {
$roles[$roleName] = 'Admin';
}
if ($roleId == 'ROLE_USER') {
$roles[$roleName] = 'User';
}
}
return $roles;
}
...
Above getRolesForForm() function will return this array, which is correct format for using with Symfony's choice field type:
Array
(
[ROLE_ADMIN] => Admin
[ROLE_USER] => User
)
And the form's not working anymore, with this exception:
The value of type "array" cannot be converted to a valid array key.
When I add 'multiple' = true to form builder, it works, but it's not a dropdown. It's HTML select box which allow multiple selection.
$builder->add('roles', 'choice', array(
'label' => 'User Role',
'choices' => $this->roleHelper->getRolesForForm(),
'multiple' => true
));
I think, for role selection, it should be a dropdown, not a multiple-selection box. How can I achieve this? Anything wrong with my code? Thank you :)
I think it should be a multiple-selection box indeed.
Take into account that a user can (and usually will) have several roles. In your case, a user who has ROLE_ADMIN also has ROLE_USER (ROLE_ADMIN "includes" ROLE_USER if you are using FOSUserBundle).
Not really sure if this may be the problem since I don't have your full code and I am not sure what self::flatArray($this->rolesHierarchy) is returning but please notice that you are using $roleName, not $roleId as your array key. If $roleName is not a string then you will get this problem.
I faced same issue lately and this is my work around (I created DataTransformer):
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
...
$builder->get('roles')->addModelTransformer(new RolesTransformer());
}
}
And DataTransformer class:
use AppBundle\Entity\User;
use Symfony\Component\Form\DataTransformerInterface;
class RolesTransformer implements DataTransformerInterface
{
public function transform($roles)
{
foreach ($roles as $role) {
if ($role !== User::ROLE_DEFAULT) {
return $role;
}
}
return $roles[0];
}
public function reverseTransform($string)
{
return [
$string, User::ROLE_DEFAULT
];
}
}
The role attribute from UserModel of FOSUserBundle expected an array. If you use a select normal, they don't return an array.
I have some form and if user isn't logged in yet i want to show some additional fields (like email, username) in this form and validate it.
Now i do it in that way:
Create new FormType:
class QuickRegisterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email')
->add('username')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\UserBundle\Entity\User',
'validation_groups' => array(
'QuickRegister'
),
));
}
public function getName()
{
return 'quick_register';
}
}
In Controller:
$user = $this->getUser();
$formBuilder = $this->createFormBuilder($message, array(
'cascade_validation' => true
));
$formBuilder->add('body', 'textarea');
if (!$user) {
$formBuilder->add('quick_register', new QuickRegisterType(), array(
'property_path' => 'sender'
));
}
In template:
{% if form.quick_register is defined %}
{# render this fields #}
{% endif %}
And just after $form->bind($request); i get valid user in $message->getSender(); Everything is good! But i need to perform some other actions just after form validation:
if ($form->isValid()) {
$sender = $message->getSender();
if (!$sender->getId()) {
// perform some work with just registered user
// like sending email, generate passwords, so on
}
}
And this actions will be generally the same for such form from all project, so i want to optimize it.
At first i think i can create new service in DI container and just call it like
$this->container->get('my.user_service')->afterQuickRegister($sender);
But now i read about Form events in symfony2 on page http://symfony.com/doc/current/cookbook/form/dynamic_form_generation.html
I don't understand right now, can i use it for my purposes? Can i create such form subscriber in DI container? And generally, can i somehow change User just after creation? What is right form event for this? Thanks!
I implement it with FormEvents.
form.type.quick_register:
class: Acme\UserBundle\Form\Type\QuickRegisterType
arguments: [ #form.type.quick_register.subscriber ]
tags:
- { name: form.type, alias: quick_register }
And pass subscriber by form.type.quick_register.subscriber service. In subscriber i check form validation in POST_BIND event and do my stuff. Great!