symfony2 Entity select tag display - symfony

I have this Symfony form with a ManyToMany relation working fine, it displays all the parties with the property name on the entity Party.
When submitted, it queries the database according to the parties that are selected and retrieves the persons that are invited to those parties.
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('parties', 'entity', array(
'class' => 'ProtoBundle:Party',
'multiple' => true,
'expanded' => false,
'property' => 'name',
'required' => false,));
}
with the parameter
'multiple' => 'true,
all the parties are displayed at the same time in a select drop down box (not what i want).
What I want is just to have one select tag with parameter
'empty_value' => 'choose a party'
, then when the user clicks on it, the values are displayed. Actually I can do this with the parameter
'multiple'=> false,
but the problem is that I get this error message:
Neither the property "parties" nor one of the methods "setParties()", "__set()" or "__call()" exist and have public access in class "Acme\ProtoBundle\Entity\Person".
Does anyone knows how to make this select tag working and bring me a detailed solution?

In first of all you should get in consideration that if you really need many to many relation when you want simple select box without multiple select.
But...
in entity you will have to check if comming value is array, and that's it:
public function setParties($parties)
{
if (!is_array($parties)) {
$parties = array($parties);
}
$this->parties = $parties;
}

Related

ChoiceType multiple attribute according to entity property (how to choose between returning a collection of entities or one entity)

I'm working on a quiz project with the Symfony framework (version 4.4) and Doctrine as ORM.
There is a ManyToOne relation between the Answer and the Question entities, as for the QuizQuestion and Answer entities. I use the QuizQuestion entity to make the link between a quiz, a question, and the selected answer(s).
I use a EntityType "QuizQuestionType" with the multiple attribute set to true to collect answers, and it works as expected :
$builder
->add('answers', EntityType::class, [
'class' => Answer::class,
'choices' => $this->fillAnswers($quizQuestion),
'expanded' => true,
'multiple' => true,
]);
The thing is, I want to be able to setup question as multiple or single choice. If I set the EntityType multiple attibute to false, I got the error :
Entity of type "Doctrine\ORM\PersistentCollection" passed to the
choice field must be managed. Maybe you forget to persist it in the
entity manager?
I could use two answers entities with a OneToMany and a OneToOne relations, but it seems a really poor design to me.
I wonder how it can be done, ideally with a property in the Question entity that indicates if it is a multiple or unique choice question. That will allow me to simply declare it in the backend (because technically, a multiple choice question may have only one good answer, so I can't calculate it by the number of answers).
Do you have any idea on how I can achieve this ?
Here is the conceptual data model :
CDM
The answer entity : https://pastebin.com/kiRTHnvL
The QuizQuestion entity : https://pastebin.com/wL3v9fwT
Thank you for your help,
EDIT 01/08/2020
As suggested by #victor-vasiloi, I added an event listener to the form type so I can setup the correct extensions. I was not able to add the transformer though. I found the solution here and created an extension to use a data transformer from the event listener :
QuizQuestionType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($builder){
$quizQuestion = $event->getData();
$form = $event->getForm();
if ($quizQuestion->getQuestion()->getIsMultiple()){
$form->add('answers', EntityType::class, [
'class' => Answer::class,
'choices' => $this->fillAnswers($quizQuestion),
'expanded' => true,
'multiple' => true,
]);
} else {
$form->add('answers', EntityType::class, [
'class' => Answer::class,
'choices' => $this->fillAnswers($quizQuestion),
'expanded' => true,
'multiple' => false,
'model_transformer' => new CollectionToAnswerTransformer(),
]);
}
})
;
}
ModelTransformerExtension
class ModelTransformerExtension extends AbstractTypeExtension
{
public static function getExtendedTypes(): iterable
{
// return FormType::class to modify (nearly) every field in the system
return [FormType::class];
}
public function buildForm(FormBuilderInterface $builder, array $options) {
parent::buildForm($builder, $options);
if (isset($options['model_transformer'])) {
$builder->addModelTransformer($options['model_transformer']);
}
}
public function configureOptions(OptionsResolver $resolver) {
parent::configureOptions($resolver);
$resolver->setDefaults(array('model_transformer' => null));
}
}
Now the form could be loaded. When submitting though (in a case of a unique answer with radio buttons), a CollectionToArrayTranformer was giving the following error :
Expected argument of type "App\Entity\Answer", "array" given at
property path "answers".
I tried a custom CollectionToAnswerTransformer, that looks like this :
class CollectionToAnswerTransformer implements DataTransformerInterface
{
/**
* #param mixed $collection
* #return mixed|string
*/
public function transform($collection)
{
if (null === $collection){
return '';
}
else
{
foreach ($collection as $answer){
return $answer;
}
}
}
/**
* #param mixed $answer
* #return ArrayCollection|mixed
*/
public function reverseTransform($answer)
{
$collection = new ArrayCollection();
$collection->add($answer);
return $collection;
}
}
But with no better results. I get the error :
Expected argument of type "App\Entity\Answer", "instance of
Doctrine\Common\Collections\ArrayCollection" given at property path
"answers".
It looks like an issue with the reverse transformer method, but if I change it to return an entity, I got the opposite error :
Could not determine access type for property "answers" in class
"App\Entity\QuizQuestion": The property "answers" in class
"App\Entity\QuizQuestion" can be defined with the methods
"addAnswer()", "removeAnswer()" but the new value must be an array or
an instance of \Traversable, "App\Entity\Answer" given...
I think I'm almost at it, but I don't know if my transformer is the way to go or if it is easier than that...
To setup questions with single choice you could use a radio button, and checkboxes for multiple choices.
Radio button is expanded "true" and multiple "false".
Checkbox is expanded "true" and multiple "true".
Code example that display checkboxes:
$builder
->add('filter', EntityType::class, array(
'class' => 'FilterBundle:Filter',
'multiple' => true,
'expanded' => true,
'required' => true
));
Source: https://symfony.com/doc/current/reference/forms/types/choice.html#select-tag-checkboxes-or-radio-buttons
And if you want to define it for each question before displaying, there could be a field on your question entity (for example a boolean "multiple").
You can dynamically set the multiple option based on the given Question using a form event listener on the Symfony\Component\Form\FormEvents::PRE_SET_DATA event, here's where you can learn more about dynamically modifying the form and form events.
Using the same logic, when the multiple option is set to true, you can add the Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer model transformer to the answers field like this $builder->get('answers')->addModelTransformer(new CollectionToArrayTransformer()); which will ensure the transformation between the Doctrine Collection and the choices array (including single choice).

How to create collection of different types of forms?

is it possible to embed dynamically different types of forms to a single collection?
Let say I have a document (using ODM) with some embedded documents. These document I need to take and create a separate form for them. Up to this point it is OK. But how can I put them in the collection type of form, so I can be able to perform auto add and delete action of form?
Or I can do it manually, no problem, but where can I put my code? After calling bindRequest on the form? Is there a chance to remove deleted items from the form, because there is a possibility that I would not pass the isValid method.
And what I have learned so far that there is no way how to modified the form after it is created. I have an abstract form builder to which I'm adding these embedded documents. It is not a collection but separate forms, because each document has it's own form type. Is it correct approach or am I wrong?
Thanks for any correction or advice
It sounds like you would create the forms for all of your embedded documents and then add those as collections to a "parent" form for your document. In that way, your top level form becomes the "collection" which you would then get the embedded data from.
So if your document is A:
class AType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->add('b', 'collection', [
'type' => new BType(),
'allow_add' => true,
'allow_delete' => true,
])->add('c', 'collection', [
'type' => new CType(),
'allow_add' => true,
'allow_delete' => true,
])->add('d', 'collection', [
'type' => new DType(),
'allow_add' => true,
'allow_delete' => true,
]);
}
}
Forms are effectively just interactive views for your data model so this sounds like the way forward unless there's something stopping you from doing it this way?

Symfony2 FOSUserBundle: accessing DB inside profile build form

I'm using FOSUserBundle on Symfony2.
I've extended the profile form type to include my fields.
I'd like to pre-populate one of those fields with one value found in DB (not in the user entity).
Basically I need to access DB from inside the buildForm.
I don't want (if possible) to override the original controller.
EDIT: I probably cannot use the "entity" field type as that (as far as I understand) creates the equivalent of a choice (with values loaded from DB). I need to have the field editable. I need to have access to the current user entity so that I have access to its ID. With that ID I can perform a query and get a text value from my DB (it's a license associated to the user) and use that value to populate one of the text fields of my form. Could I possibly override the getLicense() method of the user class to perform my queries there? How can I have access to DB inside an entity?
Hints?
Thank you!
You need the Entity field:
http://symfony.com/doc/current/reference/forms/types/entity.html
Here's an example:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('foobar', 'entity', array(
'class' => 'DummyBundle:Test',
'property' => 'name',
'multiple' => false,
'expanded' => false,
'query_builder' => function(\Doctrine\ORM\EntityRepository $er) {
return $er->createQueryBuilder('i')
->orderBy('i.name', 'ASC');
}
));
}

How do I add an unbound field to a form in Symfony which is otherwise bound to an entity?

Maybe I'm missing the obvious but how do I (or can I) add an extra "unbound" field to a Symfony form that is otherwise bound to an entity?
Let's say I have an entity with fields first_name and last_name. I do the typical thing in my form class buildForm method.
$builder
->add('first_name')
->add('last_name')
;
and this in my controller:
$editForm = $this->createForm(new MyType(), $entity);
That works nicely but I'd like to add another text box, let's call it "extra", and receive the value in the POST action. If I do $builder->add('extra')‍, it complains that
NoSuchPropertyException in PropertyAccessor.php line 479:
Neither the property "extra" nor one of the methods "getExtra()", "extra()", "isExtra()", "hasExtra()", "__get()" exist and have public access in class...
Which is correct. I just want to use it to collect some extra info from the user and do something with it other than storing it with the entity.
I know how to make a completely standalone form but not one that's "mixed".
Is this possible?
In your form add a text field with a false property_path:
$builder->add('extra', 'text', array('property_path' => false));
You can then access the data in your controller:
$extra = $form->get('extra')->getData();
UPDATE
The new way since Symfony 2.1 is to use the mapped option and set that to false.
->add('extra', null, array('mapped' => false))
Credits for the update info to Henrik Bjørnskov ( comment below )
Since Symfony 2.1, use the mapped option:
$builder->add('extra', 'text', [
'mapped' => false,
]);
According to the Documentation:
allow_extra_fields
Usually, if you submit extra fields that aren't configured in your form, you'll get a "This form should not contain extra fields." validation error.
You can silence this validation error by enabling the allow_extra_fields option on the form.
mapped
If you wish the field to be ignored when reading or writing to the object, you can set the mapped option to false.
class YourOwnFormType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
array(
'allow_extra_fields' => true
)
);
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$form = $builder
->add('extra', TextType::class, array(
'label' => 'Extra field'
'mapped' => false
))
;
return $form;
}
}

Symfony 2, how to persist join table entities?

This is such a trivial problem that I can't believe I couldn't find an answer.
Symfony 2, doctrine 2.1. I've got two entities and one intermediate entity (join table). User, Pref, and UsersPrefs. Pref table is dictionary table, so that I could change pref name in one place only. Ok, let's see the picture:
infographic http://dl.dropbox.com/u/22495762/infographic.png
As You can see, I want to have a checkbox group, with all the possible choices (prefs) and preferred choices checked. So, if there are 3 prefs, and only 2 selected by the user, there should be 3 checkboxes, 2 selected.
It's simple, if done plain PHP - query database twice to get list of all prefs and user prefs, render checkboxes depending on values, add some actions to handle form submit, done.
But for the life of God I can't get this to work using symfony & doctrine. I was able to get to the point where I can update relationships in doctrine and further in database, but I'm using raw query values for that:
$data = $request->request->get('some_form');
and this supposedly isn't the way it should be done?
Morevoer, I'm completely stuck as to how should I display checkbox list. I either get list of all options, none checked, or only user options, all checked. Or 'left joined' result set with checkboxes for all cases, useless anyway.
I've come to the point where I tried to overload twig checkbox template, but I couldn't pass variables to the form template, and that was the last straw...
EDIT:
This way I'm getting group of checkboxes, not connected to user choices:
->add('prefs', 'entity', array(
'class' => 'Some\TestBundle\Entity\Pref',
'expanded' => 'true',
'multiple' => 'true',
'property' => 'name'
))
And this way I'm getting only user choices, all checked:
->add('prefs', 'entity', array(
'class' => 'Some\TestBundle\Entity\UserPrefs',
'multiple' => 'false',
'expanded' => 'false',
'property' => 'pref.name',
'query_builder' => function(EntityRepository $er) use ($id) {
return $er->createQueryBuilder('u')
->where("u.user = :id")
->setParameter('id', $id)
;
},
))
And I tried left joins and other options, but at best I could get list of all possible options for all possible users, checked accordingly.
EDIT (POSSIBLE SOLUTION):
I'm displaying checkbox group:
->add('pref_ids', 'choice', array(
'choices' => array(
'1' => 'pref one',
'2' => 'pref two',
'3' => 'pref three',
),
'expanded' => 'true',
'multiple' => 'true'
))
I've added $pref_ids array in User entity. Now I just need to set values in array according to preferences chosen by user:
public function setPrefIds()
{
$prefs = $this->getPrefs();
$this->pref_ids = array();
foreach($prefs as $pref){
array_push($this->pref_ids, $pref->getPref()->getId());
}
return $this;
}
This way I get appropriate checkboxes checked.
Writing values to database is reversal of the process. I'm getting input values from request:
$data = $request->request->get('edit_form');
var_dump($data['pref_ids']);
Removing all user prefs:
foreach ($userPrefs as $pref){
$em->remove($pref);
}
And setting actual associations in doctrine from ids:
$entity->setPrefsById($em, $data['pref_ids']);
Here I'm passing entity manager to entity itself, but I need to refactor it, because it looks kinda messy this way.
Then $em->flush(); and that's it.
That's the best I could come up with. Probably it's overcomplicated and should be done entirely different way. Unfortunately couldn't figure out this "other way".
You need the choice field type: http://symfony.com/doc/current/reference/forms/types/choice.html
In your builder, it will be something like
$builder->add('prefs', 'choice', array('multiple' => true, 'expanded' => true, 'choices' => fetch the available prefs from the database here);
Edit: Sorry I'm mistaken, you need the "Entity" type, which fetches automatically the choices from the database: http://symfony.com/doc/current/reference/forms/types/entity.html
You must still put multiple => true expanded => true to get checkboxes.

Resources