Symfony2 Doctrine2 Many To Many Form not Saving Entities - forms

I am having some trouble with a many to many relationship. I have Users and Assets. I would like to be able to assign users to an asset on the asset page.
The code below displays a list of users when creating/editing an asset, however changes made to the user checkboxes do not save, while the rest of the data is persisted.
If I add an entry to users_assets through the mysql client, these changes are shown in the asset list.
User
class User extends BaseUser
{
/**
* #ORM\ManyToMany(targetEntity="Asset", inversedBy="users")
*/
private $assets;
}
Asset
class Asset
{
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="assets")
*/
private $users;
}
AssetType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$form = $builder
->add('users', null, array(
'expanded' => true,
'multiple' => true
))
->getForm();
return $form;
}

For some reason I had to switch the doctrine mappings to get this to work:
Asset:
/**
* #ORM\ManyToMany(targetEntity="Adaptive\UserBundle\Entity\User", inversedBy="assets")
* #ORM\JoinTable(name="user_assets")
*/
private $users;
User:
/**
* #ORM\ManyToMany(targetEntity="Splash\SiteBundle\Entity\Asset", mappedBy="users")
*/
private $assets;
Now when I save the asset it saves the users associated. I did not need to define builder->add as an entity or collection. I simply pass it null and it uses the mapping info to fill in the entity info:
AssetType:
->add('users', null, array('expanded' => "true", "multiple" => "true"))
Not exactly sure why I needed to have the inversedBy and JoinTable info on the Asset vs The User but it seems to be working now!
Thanks For The Suggestions!!!

Weird enough I faced the same problem in 2016 and still had hard time finding the solution. I will share it for future googlers:
The problem is that what symfony essentially does when you save the form is this:
$asset->getUsers()->add($user)
And because you're on the inverse side of the relation it won't persist your changes.
What you really need is to make so that it calls this:
$asset->addUser($user)
Where addUser() is defined the following way on the Asset entity:
public function addUser(User $user)
{
//add to the inverse side
$this->users->add($user);
//add on the owning side (only this is persisted)
$user->addAsset($this); //$user->assets->add($asset);
}
So in order to make symfony use that $asset->addUser() method, you should set
'by_reference' => false
on your users field for AssetType form.
More about this setting here http://symfony.com/doc/current/reference/forms/types/form.html#by-reference
Remember you also need to define removeUser() method in the same way (so that it removes entity from the owning relation)

Not exactly sure why I needed to have the inversedBy and
JoinTable info on the Asset vs The User but it
seems to be working now!
The reason why your changes has been ignored is that doctrine persists only changes by the owning side of a relation (like #Florian said).
This is the link to Doctrine's documentation where this behaviour is explained: http://docs.doctrine-project.org/en/latest/reference/unitofwork-associations.html

At first you should drop backslash prefix in annotations (see notice here).
And you need to use entity field type:
$builder->add('users', 'entity', array(
'class' => 'AdaptiveUserBundle:User',
'expanded' => true,
'multiple' => true,
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.username', 'ASC');
},
));

You need to use 'collection' field type in your form.
$builder->add('users', 'collection', array(
'type' => new UserType(),
'prototype' => true,
'allow_add' => true,
'allow_delete' => true
));
You need to create the UserType() form first obviously.
Here is all the info you will need, including code samples:
http://symfony.com/doc/current/cookbook/form/form_collections.html
http://symfony.com/doc/current/reference/forms/types/collection.html

Related

Symfony: many to many relation with text fields

We have a many to many relation with two entity Product {properties: name, details } and Tag {properties: name}. Now when a user add product, he must be able to create tags as well. So I tried this
$builder->add('tags', 'entity', array(
'class' => 'AppBundle:Tag',
'multiple' => true,
'expanded' => true,
'property' => 'name',
'required' => true
'allow_add' => true ));
Unfortunately, this gives me a drop down list of tags. But what I really want is multiple text input fields where user can enter the name of the tags and then this should save in the database when user save the category form.
All solutions I found so far are all related to either drop down or checkbox choices. But in my case, I don't have a list of tags in the database and I want to create the tags when user create a product. So how can I do that?
The entity type field could be only select, checkboxes or radio buttons (depending on expanded and multiple options), so these are not option for you.
You can solve multiple text fields rendering by implementing Tag type and embed it to your form as a collection.
Alter ProductType by collection field:
...
$builder->add('tags', CollectionType::class, array(
'allow_add' => true,
'allow_delete' => true,
'entry_type' => TagType::class,
'entry_options' => array(
...
),
)
...
Create TagType itself:
class TagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('tagName', FormType\TextType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Entity\Tag',
)
);
}
}
But be aware of downsides - if there are not any tags assigned to the Product, the won't be any fields rendered. You have basically two options - 1) add few empty tags to the Product entity before rendering the form, 2) implement some kind of javascript method to add new tag fields.

How to get choice options from another entity in Symfony

I want to load two choice list, the second one load only some values based on the first choice. But my problem comes first... how to load the EntityType values in the first list from a class that is not directly related to the current class (the form type class).
->add(
'cliente',
EntityType::class,
array(
'class' => 'AppBundle:Cliente',
'choice_label' => 'nombre',
)
)
But there is no one 'cliente' field in this entity, so it throws the message you know...
Neither the property "cliente" nor one of the methods "getCliente()",
"cliente()", "isCliente()", "hasCliente()", "__get()" exist and have
public access in class "AppBundle\Entity\Envio".
Please, do you know how to solve this issue? Any help is welcome!
According to your error your form is for the entity Envio. If you want to create an EntityType choicelist based on the Cliente entity, you'll need a doctrine relation in your Envio class:
class Envio
{
/*
* #ORM\ManyToOne(targetEntity="Cliente")
*/
protected $cliente;
The error has no relation to your question about having 2 choicelists and changing the choices of your second list based on the first choice. You're probably best off using javascript for that and you'll have many choices from AJAX to limiting the choices on the fly depending on the value or innerText of the .
For that error you need to make the field as 'mapped' => false, so:
->add(
'cliente',
EntityType::class,
array(
'class' => 'AppBundle:Cliente',
'choice_label' => 'nombre',
'mapped' => false
)
)
Then for getting the property in the controller you must do:
$cliente = $form->get('cliente')->getData();
Hope this help you.

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.

Symfony 2.3 pass custom data to entity form, using choice or other type

SETUP:
Main entity with a related entity with ManyToOne relation.
Main entity has a formType with the related entity added.
The related entity is a big object with a lot of fields and related objects, and very slow to get.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('relatedEntity', 'entity', array(
'class' => 'ProjectName\RelatedEntityBundle\Entity\RelatedEntity',
'query_builder' => function (EntityRepository $er) {
$queryBuilder = $er->createQueryBuilder('relatedEntity');
$queryBuilder->resetDQLPart('select');
$queryBuilder->resetDQLPart('from');
$queryBuilder->select('relatedEntity')
->distinct(true)
->from('ProjectNameRelatedEntityBundle:RelatedEntity', 'relatedEntity');
return $queryBuilder;
},
....
....
}
Template:
(relateEntity has a __toString() function defined to show its name).
{{ form_label(form.relatedEntity) }}
{{ form_widget(form.relatedEntity) }}
{{ form_errors(form.relatedEntity) }}
QUESTIONS:
The Main entity as shown above, will get all objects and pass them
to the template. It works perfectly but it is very slow since the
related entity objects are big and the query may take more 10
seconds to finish hydrating all the object data.
How could I select only some fields from my related entity and show them in the template without getting all objects hydrated?
Is it possible to use the choice option or another type instead of
the default entity type to get only some fields of the related
entity and show them in the template?
How could I build a custom query hydrated as a simple array of key value, and pass that array to the formType, to the queryBuilder of my related entity field?
Finally, in case its not possible to get only some fields to be
shown in the template, should I avoid symfony 2 forms and make a
custom management of the related entity?
TESTS:
I cant seem to build the form with the choice type by passing just an array to show a selectBox with the id and name of my related entity in the template. I always get the same error, asking me to insert an array of entity objects in that choiceS option.
Lets look at some examples at the formType, buildForm function of the main entity:
WORKS, default Symfony 2 generated code with null type:
->add('relatedEntity', null, array('label'=> 'relatedEntity'))
WORKS, with 'entity' type and a simple queryBuilder:
->add('relatedEntity', 'entity', array(
'class' => 'ProjectName\RelatedEntityBundle\Entity\RelatedEntity',
'query_builder' => function (EntityRepository $er) {
$queryBuilder = $er->createQueryBuilder('relatedEntity');
$queryBuilder->resetDQLPart('select');
$queryBuilder->resetDQLPart('from');
$queryBuilder->select('relatedEntity')
->from('ProjectNameRelatedEntityBundle:RelatedEntity', 'relatedEntity');
return $queryBuilder;
},
'property' => 'descripcion'
))
DOESNT WORK with 'choice' type, with 'choices' option passing an array of values:
$arrayValues = array('1'=>'name1', '2'=>'name2', '3'=>'name3');
->add('relatedEntity', 'choice', array(
'choices' => $arrayValues,
'multiple' => false,
'label'=> 'relatedEntity'
))
DOESNT WORK with 'entity' type, with 'choices' option passing an array of values:
$arrayValues = array('1'=>'name1', '2'=>'name2', '3'=>'name3');
->add('relatedEntity', 'entity', array(
'class' => 'ProjectName\RelatedEntityBundle\Entity\RelatedEntity',
'choices' => $arrayValues ,
'multiple' => false,
'label'=> 'relatedEntity'
))
I have also tested trying to hack the choices input requeriment by building an array of objets of my related entity, but it asks me to persists those entities before being sent to the choice type.
The problem is your form element which requires its content to be an entity, which is an instance of class ProjectName\RelatedEntityBundle\Entity\RelatedEntity, but you pass an array as choices:
$arrayValues = array(
'1'=>'name1',
'2'=>'name2',
'3'=>'name3'
);
On the other hand, when you use a choice-element and add the array, your form element will return a string, whereas your entity requires relatedEntity to be an instance of the above mentioned class.
Either way, you have to ensure the data you add or retrieve from the element matches your requirements.
What you can do, is make it a choice-element and remove the class-restriction (as you have tried). Then, to ensure it will return an entity-instance rather than a string you can use Form Events. You could use FormEvents::SUBMIT or FormEvents::PRE_SUBMIT to check which entity name was selected and perform a query to fetch the corresponding entity, e.g. something like:
$objectRepository->findEntityBy(array('name' => $name));

Symfony 2 form + showing data from a relation

I am using Symfony 2 with doctrine. I currently have an entity called Worker and in the Worker entity there is a Many To One relationship with a User entity.
/**
* #ORM\ManyToOne(targetEntity="User")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
There are more entities like Worker as well such as Manager and such. I want to create a form that creates a Job entity. In the form I am trying to create a select option that selects a Worker, but the worker's name is stored in the user database. Is there any way to print the worker's name from the user database in the form options.
$builder->add('workers','entity', [
'label' => 'Workers:',
'property' => 't.user.firstName',
'empty_value' => 'Choose a Worker',
'class' => 'Company\CompanyBundle\Entity\Worker',
'query_builder' => function (\Company\CompanyBundle\Repository\WorkerRepository $repository) {
return $repository->createQueryBuilder('t')
->add('orderBy', 't.user.firstName ASC');
}
]);
Any ideas?
I think that it would be enoough to do something like this:
$builder->add('workers', 'entity', array(
'class' => 'Company\CompanyBundle\Entity\Worker',
) );
Besides, you should implement a "__toString()" method in your Worker entity where you would return whatever you want to show (in this case, the worker name), so your __toString method in the Worker entity would be something like this:
function __toString() {
return $this->getName();
}
It's the way I usually implement this kind of relations, I hope it helps!
If you prefer, you could do this other option:
$builder->add('workers', 'entity', array(
'class' => 'Company\CompanyBundle\Entity\Worker',
'property' => 'property_name'
));
If you defined the option "property" you don't need implement the "_toString()" in the entity class