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

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));

Related

Symfony4 Forms - How do you conditionally disable a form field?

So what is the best way to have a form render effectively the same form over and over again, with conditionally disabled fields based on the Entity's property values?
I have an Invoice Entity and need a form for creating the invoice, and also the same form with various fields disabled at various stages of the invoicing process (generated, sent, paid etc).
I think the simplest answer is to disable them dynamically in the twig template via form_row options but surely this will affect server side validation of the form as it is not aware the field has been disabled?
What is the best way to disbale a field based on a value in the database?
EDIT 1:
Changed question from Dynamically disable a field in the twig template or seperate class for each form? to Symfony4 Forms - How do you conditionally disable a form field?
Thanks to #Cerad. The answer is in fact Form Events
In the form type (App\Form\InvoicesType for me), add a method call to the end of the builder:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$plus_thirty_days = new \DateTime('+28 days');
$builder
->add('client', EntityType::class, array(
'class' => Clients::class,
'choice_label' => 'name',
'disabled' => false,
) )
// the event that will handle the conditional field
->addEventListener(
FormEvents::PRE_SET_DATA,
array($this, 'onPreSetData')
);;
}
and then in the same class, create a public method named the same as the string in the array (onPreSetData for this example):
public function onPreSetData(FormEvent $event)
{
// get the form
$form = $event->getForm();
// get the data if 'reviewing' the information
/**
* #var Invoices
*/
$data = $event->getData();
// disable field if it has been populated with a client already
if ( $data->getClient() instanceof Clients )
$form->add('client', EntityType::class, array(
'class' => Clients::class,
'choice_label' => 'name',
'disabled' => true,
) );
}
From here you can update the field to be any valid FormType and specify any valid options as you would a normal form element in the From Builder and it will replace the previous one, laving it in the same original position in the form.

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.

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

How to use the ChoiceListInterface in Symfony 2?

I'd like to display a dynamic list of checkboxes in a form.
So far, I built a form embedding a static list of checkboxes, and I created a Tag entity for different values in different languages and populated the database. I'd like to replace the static checkboxes by a dynamic list based on the Tag entity.
The documentation says I should use the ChoiceListInterface. But it is really poorly documented. Would you have an example or a global logic explanation to help me ?
You can extend LazyChoiceList abstract class and implement loadChoiceList() method, create a service of it, inject it to the form and set it as choice_list option.
Finally, I used an entity field type :
->add('tags', 'entity', array(
'class' => 'bndMyBundle:Tag',
'query_builder' => function(EntityRepository $er){
return $er->createQueryBuilder('t')
->orderBy('t.en', 'ASC');
},
'expanded' => true,
'multiple' => true,
'property' => 'en',
))
Then, I just need to replace the 'en' value by the user's current locale to choose the right language.

Symfony2 Doctrine2 Many To Many Form not Saving Entities

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