Entity form field with inactive records - php

I have a form definition like this (for demonstration purposes):
$builder->add('field', 'entity', [
'class' => EntityA::class,
'query_builder' => function($repo) {
return $repo->createQueryBuilder('e')
->andWhere('e.active = 1');
}
]);
This ensures that only active records can be selected in the dropdown field when using this form.
This leads to this case: When I edit an entity using the form definition from above, and this entity as an inactive EntityA assigned, it won't appear in the dropdown field. When I hit the save button, it will get the first active (if any) EntityA assigned. Also the form will suggest to the user that a different Entity is assigned than it actually is.
The correct way would be that the form displays all active records and the one inactive one that is currently assigned.
I looked into Form event listeners but this seems overly complicated. Also, extending the form just for editing could be a thing but it seems not "the right way" to me.
How can I solve this issue, preferably without using 3rd party bundles?

Get the object and load different data into dropdown depends of type of action: edit/create:
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
$form ->add('field', 'entity', [
'class' => EntityA::class,
'query_builder' => function($repo) use ($data) {
if ($data->getId()) {
// Edit mode: append the pre-selected record to dropdown
return $repo->createQueryBuilder('e')
->andWhere('e.active = 1')
->orWhere('e.id = :id')
->setParameter('id', $data->getId());
} else {
// Display only active records
return $repo->createQueryBuilder('e')->andWhere('e.active = 1');
}
}
]);
});

Related

"Unable to reverse value for property path" for Symfony2 form ChoiceType field on Select2

Long story short, in Symfony 2.8 I've got Movie entity with actors field, which is ArrayCollection of entity Actor (ManyToMany) and I wanted the field to be ajax-loaded Select2.
When I don't use Ajax, the form is:
->add('actors', EntityType::class, array(
'class' => Actor::class,
'label' => "Actors of the work",
'multiple' => true,
'attr' => array(
'class' => "select2-select",
),
))
And it works.
I tried to put there an empty Select field:
->add('actors', ChoiceType::class, array(
'mapped' => false,
'multiple' => true,
'attr'=>array(
'class' => "select2-ajax",
'data-entity'=>"actor"
)
))
The Select2 Ajax works, everything in DOM looks the same as in previous example, but on form submit I get errors in the profiler: This value is not valid.:
Symfony\Component\Validator\ConstraintViolation
Object(Symfony\Component\Form\Form).children[actors] = [0 => 20, 1 => 21]
Caused by:
Symfony\Component\Form\Exception\TransformationFailedException
Unable to reverse value for property path "actors": Could not find all matching choices for the given values
Caused by:
Symfony\Component\Form\Exception\TransformationFailedException
Could not find all matching choices for the given values
The funny part is the data received is the same as they were when it was an EntityType: [0 => 20, 1 => 21]
I marked field as not mapped, I even changed field name to other than Movie entity's field name. I tried adding empty choices, I tried to leave it as EntityType but with custom query_builder, returning empty collection. Now I'm out of ideas.
How should I do it?
EDIT after Raymond's answer:
I added DataTransformer:
use Doctrine\Common\Persistence\ObjectManager;
use CompanyName\Common\CommonBundle\Entity\Actor;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class ActorToNumberTransformer implements DataTransformerInterface
{
private $manager;
public function __construct(ObjectManager $objectManager)
{
$this->manager = $objectManager;
}
public function transform($actors)
{
if(null === $actors)
return array();
$actorIds = array();
foreach($actors as $actor)
$actorIds[] = $actor->getId();
return $actorIds;
}
public function reverseTransform($actorIds)
{
if($actorIds === null)
return array();
foreach($actorIds as $actorId)
{
$actor = $this->manager->getRepository('CommonBundle:Actor')->find($actorId);
if(null === $actor)
throw new TransformationFailedException(sprintf('An actor with id "%s" does not exist!', $actorId));
$actors[] = $actor;
}
return $actors;
}
}
Added it at the end of the MovieType buildForm():
$builder->get('actors')
->addModelTransformer(new ActorToNumberTransformer($this->manager));
$builder->get('actors')
->addViewTransformer(new ActorToNumberTransformer($this->manager));
And added service:
common.form.type.work:
class: CompanyName\Common\CommonBundle\Form\Type\MovieType
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: form.type }
Nothing changed. On form submit, reverseTransform() gets the proper data, but profiler shows the same error. That's a big mistery for me now...
You'll need to add a DTO (Data Transformer ) to transform the value received from your form and return the appropriate object .
Since you're calling the value from Ajax it doesn't recognized it anymore as a an object but a text value.
Examples :
Symfony2 -Use of DTO
Form with jQuery autocomplete
The correct way isn't Data Transformer but Form Events, look here:
http://symfony.com/doc/current/form/dynamic_form_modification.html#form-events-submitted-data
In the example you have the field sport (an entity, like your Movie) and the field position (another entity, like actors).
The trick is to use ajax in order to reload entirely the form and use
PRE_SET_DATA and POST_SUBMIT.
I'm using Symfony 3.x but I think it's the same with 2.8.x
When you add data transformers and nothing seems to change, it sounds like the data never goes through your data transformers. The transformation probably fails before your new data transformers are called. Try to add a few lines to your code:
$builder->get('actors')->resetViewTransformers();
$builder->get('actors')->resetModelTransformers();
// and then add your own

Symfony 2 Entity Field Type not selecting existing data in edit action

I have an entity field type I'm using in an 'edit' form.
The field type lists the correct options and the data persists, but it selects the 'top' result in the list by default, and not the data that is in the DB.
So for example if I have a record with the shelf marked as SH6, and I go to edit, the default shelf selected in the entity field type will be whatever is at the top of the list, ie SH1.
This means that users might go to edit unitsInStock and accidentally change the shelf value, because they didn't realise it was set to the wrong thing. Even more annoying is that even if you know about the problem, you may not remember the value it is supposed to be set to.
This is my controller action.
public function editAction($id, Request $request) {
$em = $this->getDoctrine()->getManager();
$article20000stock = $em->getRepository('RegenerysQMSBundle:Article20000Stock')->find($id);
if (!$article20000stock) {
throw $this->createNotFoundException(
'No id ' . $id
);
}
$form = $this->createFormBuilder($article20000stock)
->add('article20000Information')
->add('unitsInStock')
->add('expiryDate')
->add('shelf', 'entity', array('class' => 'RegenerysQMSBundle:Shelf', 'property' => 'id', ))
->add('submit', 'submit')
->getForm();
$form->handleRequest($request);
if ($form->isValid()) {
$em->flush();
return $this->redirectToRoute('regenerys_qms_article20000stock_individual', array('id' => $id));
}
$build['form'] = $form->createView();
return $this->render('forms/editArticle20000Stock.html.twig', $build);
}
So the Entity was lacking a relationship, which is what was causing the issue.

Symfony unmapped entity form field not having data

In Symfony 2.6, I am using an entity form type that is unmapped:
$form
->add(
'myEntity', // Form field name
'entity',
[
'mapped' => false, // Not mapped
'class' => 'MyVendor\MyBundle\Entity\MyEntity',
'choices' => $MyEntityCollection, // list of MyEntity
'property' => 'name',
'empty_value' => 'Please select MyEntity',
'empty_data' => null,
'attr' => [
'label' => 'My label'
]
]
);
This allows user to properly select an item of MyEntity or leave it blank. According to that, I am adding a EventSubscriber to modify the preSubmitted data if any value is selected, and leave it as it is if no choice has been made.
Here is the eventSubscriber:
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
FormEvents::PRE_SUBMIT => 'preSubmitData'
];
}
/**
* #param FormEvent $event
*/
public function preSubmitData(FormEvent $event)
{
if( null === ($entity = $event->getForm()->get( 'myEntity' )->getData() ) ){
return;
}
// Set value if field has been defined
$event
->getForm()
->setData( $entity )
;
}
If user selects a choice other than blank, when I debug the preSubmitData function:
$event->getForm()->get('entity')->getData() gives null
$event->getData() gives an array having as 'entity' key the selected entity ID (just the scalar value)
My questions are:
Shouldn't $event->getForm()->get('entity')->getData() have the selected entity?
Why is $event->getForm()->get('entity')->getData() giving null if $event->getData() has at least the entity ID in it?
Is there any way to get the entity here (as it happens with the mapped entities) without having to call the entity manager and querying the entity via its ID?
Thanks in advance!
Edit
For the big picture, in my global form (other fields not described here) I have 2 depending fields:
A select A (not described here) with some options from a tree. This option does exist in the global form entity as a property.
A second B select named myEntity (described here). It doesn't exist as the global form entity as a property, thus the mapped = false. If any choice is made here, then the first select (A)'s option is overridden by this one. Else the first choice remains as the entity property value.
Hope is clearer now.
Ok, it was giving null because we are on the preSubmit event, and here data sent is not yet mapped within an entity.
Changing the event to submit gives the mapped entity as needed.

Symfony2 Saving data of related Entity after submiting form

I'm starting developing with Symfony2 and looks like I need help. I have Product entity related with SynchronizationSetting entity. I can edit product data by form maped with his entity. But I also need to modify some data related to product in SynchronizationSetting. To do that I've modified the form so it look like that (Vendor\ProductBundle\Form\ProductType.php):
...
->add('synchronization_setting', 'choice', array(
'choices' => array('daily' => 'Daily', 'weekly' => 'Weekly', 'never' => 'Never'))
After form is submitted selected checkbox values are passed to setSynchronizationSetting method in Product Entity. Then I do that (Vendor\ProductBundle\Entity\SynchronizationSetting.php):
public function setSynchronizationSetting($data)
{
$synchronizationSetting = new SynchronizationSetting();
$synchronizationSetting->setDaily(in_array('daily', $data) ? '1' : '0');
...
}
And now I need to somehow save those SynchronizationSetting entity into database. I read that calling entity manager from here is very bad practice so... how should I save this?
One possible way (I'm not sure if it's good practice)
public function setSynchronizationSetting($data)
{
$synchronizationSetting = new SynchronizationSetting();
$synchronizationSetting->setDaily(in_array('daily', $data) ? '1' : '0');
}
public function retSynchronizationSetting()
{
return $this->synchronizationSetting;
}
Then in your controller in place where you handle form data you call retSynchronizationSetting() and save entity using EntityManager.

Symfony Forms, sub/embedded forms

I would like to update multiple entities and with the update I would like to add a field called "comment". This field is not related to the entity; I will save the comment in a separate table.
This is what I've tried (Controller code below) - this is just a sample of the code, but should give an idea of what I am trying to do:
$form = $this->createFormBuilder();
foreach (array(1,2,3) as $id) {
$subform = $this->createFormBuilder()
->add('story', 'entity', array(
'class' => 'AcmeDemoBundle:Story',
'query_builder' => function($em) use ($id) {
$qb = $em->createQueryBuilder('s')
$qb->select(array('j'))
->add('from', 'AcmeDemoBundle:Story')
->addWhere('j.id = :id')
->setParameter('id', $id);
return $qb;
},
'property' => 'id',
'required' => true, 'expanded' => true, 'multiple' => true
)
->add('comment', 'textarea');
}
$form->add($subform, '', array('label' => '');
...
// then I send the form to the template with $form->createView()
What I expected to see was a form with each entity (1,2,3) as a checkbox and next to that a comment block. Instead what happens is I only get one checkbox and one comment and always for the last entity in the array (in the above case, entity number 3). It seems that the form builder ignores all the subforms that I add and only takes the last one - this is also the case when looking at the SQL that goes to the DB, there is only a select for # 3 and no selects for 1 and 2.
Given that the html produced supports what I am trying to do, I expect the above to work:
<input type="checkbox" id="form_form_story_0" name="form[form][story][]" value="3">
<textarea id="form_form_comment" name="form[form][comment] />
I've also tried giving the fields unique names - for example ->add('story_' . $id) etc, but that didn't do anything. I also tried giving each querybuilder a unique name $em->createQueryBuilder('s'.$id) but that didn't work either.
Also, it doesn't work if I remove the query_builder (and just use the class)
Update: it doesn't seem to have anything to do with the entity, even if I try and create subforms with plain text fields it doesn't work...
Update 2 if the sub form has a different name using:
$subForm = $this->get('form.factory')->createNamedBuilder("form$id"), 'form', array())
then it works. Unfortunately this means that I can't loop through the subforms inside twig.
It can't work as stated/asked above.
->add
overwrites a previous copy of that child, as seen here:
Symfony\Component\Form
...
public function add(FormInterface $child)
...
$this->children[$child->getName()] = $child;
To "solve" the problem I discarded the idea of sub forms and named each item separately like this:
$form
...
->add("comment_$id", 'textarea')
made an array of the entities and in twig render the comment field for that entity like this:
{% for entity in entities %}
...
{% set child = "comment_" ~ entity.id %}{{ form_widget(form.children[child]) }}
...
Your example code contains error: you adding $subform after cycle, so you explicitly adding only last generated $subform object.
Noted line $this->children[$child->getName()] = $child; prevents from "simple" way to add subform builders, but there is a workaround:
Instead of calling $this->createFormBuilder() helper for subform (this will give it name "form"), you can create biulder with $this->container->get('form.factory')->createNamedBuilder('sub_form_1') with explicit name.

Categories