Multiple ChoiceType, array expected - php

I am trying to make a multiple-select ChoiceType in my Symfony form. However, I am getting the following error:
Unable to transform value for property path "[organisations]":
Expected an array.
Note: If I change the name of the component from organisations to anything else, it's rendering the form correctly.
As far as I can see, organisations is an array:
/**
* #var Organisation[]
* #ORM\ManyToMany(targetEntity="Booking\Entity\Organisation", inversedBy="users")
* #ORM\JoinTable(name="users_organisations")
*/
protected $organisations;
Here's the form:
$organisations = $doctrine->getRepository('Booking\Entity\Organisation')->findAll();
$builder
->add('organisations', ChoiceType::class, array(
'choices' => $organisations,
'choice_label' => function($organisation, $key, $index) {
return $organisation->getName();
},
'multiple' => true
))
What am I doing wrong?

Use EntityType instead.
$builder
->add('organisations', EntityType::class, array(
'choices' => $organisations,
'choice_label' => function($organisation, $key, $index) {
return $organisation->getName();
},
'multiple' => true
))
I posted this just because people (like me) don't usually read all the comments. Merit goes to Jakub.

Related

symfony forms: get a second attribute in label for a select element

I have an entity Institution. In a form a user can select one or more. I use Select2 for the font-end. An institution has the attribute internationalName, which is the default attribute because of:
Institution.php
public function __toString()
{
return $this->internationalName;
}
An institution can also have an abbreviated name, as attribute abbreviation. What I would like, is to use this second attribute to show (if it exists) in the select form. Even better would be that it's not shown but that you can search for it, but I really don't know if this is possible at all.
I could change __toString() so that it includes the abbreviation, but this is unwanted because of other forms, so I am trying to make it show only in this form through
LocationType.php
->add('Institutions', EntityType::class, [
'class' => Institution::class,
'label' => 'Connected Institution(s)',
'multiple' => true,
'attr' => ['data-select' => 'true', 'data-placeholder' => 'start typing to find your institution...'],
'constraints' => array(
new Count(array(
'min' => 1,
'minMessage' => "Select at least one institution."))),
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('i')
->orderBy('i.internationalName', 'ASC');
},
I tried to use 'choice_label' => 'abbreviation' (just as a test), but that makes all labels blank, which I really don't understand why. I also tried 'choice_label' => 'internationalName'.'abbreviation', but that won't work because there is no property internationalNameabbreviation. I thought about creating a new attribute where I combine the two, but given that 'choice_label' => 'abbreviation' already results in a blank list I don't think this would work. Any other options or solutions?
edit: as requested the relevant entity class part,
Institution.php
/**
* #Assert\NotBlank(message="Please enter the international name.")
* #ORM\Column(type="string")
*/
private $internationalName;
/**
* #ORM\Column(type="string", nullable=true)
*/
private $abbreviation;
Maybe something like:
https://symfony.com/doc/current/reference/forms/types/choice.html#choice-value
->add('Institutions', EntityType::class, [
'class' => Institution::class,
'label' => 'Connected Institution(s)',
'query_builder' => function (EntityRepository $er) {
return $er
->createQueryBuilder('i')
->orderBy('i.internationalName', 'ASC')
;
},
'choice_value' => function (Institution $institution = null) {
return $institution ? $institution->getInternationalName() . '(' . $institution->getAbbreviation() . ')' : '';
},
])

Symfony form - Choicetype - Error "Array to string covnersion"

I'm running Symfony 3.4 with FosUserBundle.
I customized the registration form in order to get the Roles included :
<?php
$form->add('roles', ChoiceType::class, array(
'choices' => array(
'user' => 'ROLE_USER',
'admin' => 'ROLE_ADMIN'
),
'label' => 'Role :',
'expanded' => true,
'multiple' => true
));
Then roles are now selectable in the registration form through 2 new checkbox... but I'd like to display it with a <select> tag. When I set the expanded option to false I get a select, but if I set the multiple option to FALSE I get an error :
Array to string conversion
Any idea to fix my issue ?
EDIT : below is the stack trace :
Symfony\Component\Debug\Exception\ContextErrorException:
Notice: Array to string conversion
at vendor\symfony\symfony\src\Symfony\Component\Form\ChoiceList\ArrayChoiceList.php:73
at Symfony\Component\Form\ChoiceList\ArrayChoiceList->Symfony\Component\Form\ChoiceList\{closure}(array('ROLE_USER'))
at call_user_func(object(Closure), array('ROLE_USER'))
(vendor\symfony\symfony\src\Symfony\Component\Form\ChoiceList\ArrayChoiceList.php:158)
at Symfony\Component\Form\ChoiceList\ArrayChoiceList->getValuesForChoices(array(array('ROLE_USER')))
(vendor\symfony\symfony\src\Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer.php:32)
at Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer->transform(array('ROLE_USER'))
(vendor\symfony\symfony\src\Symfony\Component\Form\Form.php:1104)
at Symfony\Component\Form\Form->normToView(array('ROLE_USER'))
(vendor\symfony\symfony\src\Symfony\Component\Form\Form.php:350)
at Symfony\Component\Form\Form->setData(array('ROLE_USER'))
(vendor\symfony\symfony\src\Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper.php:49)
at Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper->mapDataToForms(object(User), object(RecursiveIteratorIterator))
(vendor\symfony\symfony\src\Symfony\Component\Form\Form.php:383)
at Symfony\Component\Form\Form->setData(object(User))
(vendor\friendsofsymfony\user-bundle\Controller\RegistrationController.php:70)
at FOS\UserBundle\Controller\RegistrationController->registerAction(object(Request))
at call_user_func_array(array(object(RegistrationController), 'registerAction'), array(object(Request)))
(var\cache\dev\classes.php:4659)
at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1)
(var\cache\dev\classes.php:4614)
at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true)
(vendor\symfony\symfony\src\Symfony\Component\HttpKernel\Kernel.php:200)
at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
(web\app_dev.php:29)
to solve your problem you need a data transformer, because the roles field is actually an array :
add the data transformer to your FormType class along with the roles field instead of adding it in the controller:
$builder->add('roles', ChoiceType::class, array(
'choices' => array(
'user' => 'ROLE_USER',
'admin' => 'ROLE_ADMIN'
),
'label' => 'Role :'
));
//roles field data transformer
$builder->get('roles')
->addModelTransformer(new CallbackTransformer(
function ($rolesArray) {
// transform the array to a string
return count($rolesArray)? $rolesArray[0]: null;
},
function ($rolesString) {
// transform the string back to an array
return [$rolesString];
}
));
2020 - Symfony 5.0.8:
in your FormType file:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('roles', ChoiceType::class, [
'required' => true,
'multiple' => false,
'expanded' => false,
'choices' => [
'User' => 'ROLE_USER',
'Admin' => 'ROLE_ADMIN',
],
]);
$builder->get('roles')
->addModelTransformer(new CallbackTransformer(
function ($rolesArray) {
// transform the array to a string
return count($rolesArray)? $rolesArray[0]: null;
},
function ($rolesString) {
// transform the string back to an array
return [$rolesString];
}
));
}
What is happening is normal : Your property roles is AN ARRAY of string. In your entity you got something like :
/**
* #var array
*/
private $roles = [];
And logically, when you put the option multiple => 'true', Symfony form will treat it like an array and convert each element of your array in string (which they already are).
On the other hand, if you put multiple => false, it will consider the array as a single choice which it is not.
Simply put, you have an inconsistency between your model where your roles property is an array of string, and the form you are trying to create, which considers roles as a single value.
In your case, You have to put multiple => true.

Product and Sizes symfony2 doctrine example

I'm new to symfony2 and I'm building my first online store with it. I have products and I want to add product sizes, one product can have many sizes and one size can have many products. For example: two products cat have 'M' size.
class Product {
...
/**
* #ORM\ManyToMany(targetEntity="Size", inversedBy="products", cascade={"persist", "merge"})
* #ORM\JoinTable(name="sizes")
*/
private $sizes;
}
//in another file
class Size {
/**
* #ORM\ManyToMany(targetEntity="Product", mappedBy="sizes")
*/
protected $products;
}
ProductController.php
...
->add('sizes', CollectionType::class, [
'entry_type' => SizeType::class,
'label' => 'Sizes',
'allow_add' => true,
])
...
SizeType.php
public function buildForm(FormBuilderInterface $builder, array $options) {
$repo = $this->em->getRepository('AppBundle:Size');
$q = $repo->createQueryBuilder('c')
->getQuery();
$sizes = $q->getResult();
$builder->add('name', EntityType::class, array(
'class' => 'AppBundle:Size',
'choice_label' => 'name',
));
}
Right now I'm getting
Catchable Fatal Error: Object of class AppBundle\Entity\Size could not be converted to string I can fix if I implement __toString() but I don't know if this is the right thing to do, and if I do this, when editing the product, the dropdown doesn't select the right size.
My question is, is this the right way to implement product - sizes function to online store?
Try with this code:
$builder->add('name', EntityType::class, array(
'class' => 'AppBundle:Size',
'choice_label' => 'name',
'property' => 'needed_property_name' //just write the needed property name there
));
So I've figured out better way to do it, 'entity' type with multiple => true
->add('sizes', 'entity', [
'class' => Size::class,
'label' => 'Размери',
'choice_label' => 'name',
'multiple' => true,
'expanded' => false,
//'allow_add' => true,
])
This way multiple sizes can be selected, with bootstrap-multiselect I've made it good looking and perfectly works for me now.
I'd love to hear if there is a better way.
Product annotations looks wrong. The JoinTable is a lookup table for many-to-many relation:
The convention is to name it after linked tables: products_sizes in your case:
class Product {
...
/**
* #ORM\ManyToMany(targetEntity="Size", inversedBy="products", cascade={"persist", "merge"})
* #ORM\JoinTable(name="products_sizes")
*/
private $sizes;
}

Symfony Entities in a collection

I couldn't find any similar questions so here goes:
I have a Many-To-Many relationship between my (order)Flow Entity and my Product Entity, but it doesn't select an <option> in the <select>.
All the Entities has been setup by Symfony itself so the #ORM\ManyToMany etc. should be ok.
My controller has the following line:
$form = $this->createForm(new \FDM\BestilBundle\Type\FlowsType(), $flow)->add('submit', 'submit');
The $flow is populated correctly from the db.
The FlowsType file has amoung many fields the following code:
->add('product', 'collection', [
'type' => new ProductDropdownType(),
'allow_add' => TRUE,
'allow_delete' => TRUE,
'prototype' => TRUE,
'by_reference' => FALSE,
]
)
All fields in the FlowsType are filled out correctly, except the one below.
The ProductDropdownType has the following code:
$builder->add('name', 'entity', [
'class' => 'FDMBestilBundle:Flows',
'property' => 'name',
'by_reference' => false
]);
The number of rows is correct - if I have three rows in my many-to-many sql table it shows three rows. They just aren't selected.
If I change the ProductDropdownType to:
$builder->add('name', 'text');
The data is showing just fine - so the db and everyting is working. Except when I want a collection of <select>...
The relations are the following in the Entities:
In the Flow Entity:
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="FDM\BestilBundle\Entity\Product", inversedBy="flow")
* #ORM\JoinTable(name="productsinflows",
* joinColumns={
* #ORM\JoinColumn(name="flow", referencedColumnName="flowId")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="product", referencedColumnName="productId")
* }
* )
*/
private $product;
In the Product Entity:
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="FDM\BestilBundle\Entity\Flows", mappedBy="product")
*/
private $flow;
Can someone please help me - I'm stuck!
You can use query builder in your form, this is an exemple :
In the query builder, create the SQL query with DQL
->add('espece', 'entity', array(
'class' => 'ADMapecheBundle:Espece',
'property' => 'nom',
'multiple' => false,
'query_builder' => function(EspeceRepository $er) use ($user) {
return $er->createQueryBuilder('p')
->where("p.user = :user")
->orderBy('p.nom', 'ASC')
->setParameter('user', $user);
I hope to help you, friendly

Processing of non-existent collection fields in SonataAdminBundle forms

I have an entity class ContactsPage to store some information about emails, phones etc. The problem is to get all this information into one field "contacts" in json-format defined within the ContactsPage entity:
class ContactsPage
{
...
/**
* #var string
*
* #ORM\Column(name="contacts", type="text", nullable=true)
*/
private $contacts;
...
}
ContactsPageAdmin form constructing example for emails:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('emails', 'collection',
array(
'mapped' => false,
'required' => false,
'type' => 'text',
'allow_add' => true,
'allow_delete' => true,
)
);
}
How and where can I get this "emails" array (or collection)?
Where can I handle this array to make json and push it into "contacts" field before saving the ContactsPage entity?
Where and how I can handle "contacts" field and transfer all decoded from json information into editing form (into "emails" collection)?
Thanks.
Spent almost the day but found answers by myself.
First and second questions.
I have to define prePersist and preUpdate methods within the admin-class with my Entity as an argument, where i can get and handle "emails" array:
class ContactsPageAdmin extends Admin
{
...
public function prePersist($contactsPage)
{
$emails = $this->getForm()->get('emails')->getData();
$contactsPage->setContacts(json_encode($emails));
}
public function Update($contactsPage)
{
$emails = $this->getForm()->get('emails')->getData();
$contactsPage->setContacts(json_encode($emails));
}
...
}
What about third question. I just have to use $this->getSubject() to get original Entity and 'data' property to load needed data:
class ContactsPageAdmin extends Admin
{
...
protected function configureFormFields(FormMapper $formMapper)
$entity = $this->getSubject(); // getting original Entity
$json = $entity->getContacts(); // field with json data
$array = json_decode($json, true);
// preparing data to fulfill necessary form fields
$array['emails'] = is_array($array['emails']) ? $array['emails'] : array();
$formMapper
->add('emails', 'collection',
array(
'mapped' => false,
'required' => false,
'type' => 'text',
'allow_add' => true,
'allow_delete' => true,
'data' => $array['emails'] // embeding prepared data into collection
)
);
}
...
}

Categories