Symfony2: Persisting embedded collection within self-referencing embedded collection - symfony

Overview
I have two entities;
Element
ElementAnswer
Element has a self-referencing relation and ElementAnswer has a One-To-Many relation with Element (One Element can have many ElementAnswers).
I'm using Symfony2 with embedded collection and it's is working (together with javasript). I can add multiple 'child' elements to the 'parent' element. Also i can add multiple answers to the child element. But.. When i add a child element and add answers to the child element only the child element gets persisted. The answers (which is an embedded collection to the child elements) don't get persisted. AND when i edit the whole collection the answers are persisted.
To be more precise i made some screenshots AND i debugged the post data. Which is exactly the same.
ElementType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('question')
//->add('answers')
->add('answers', 'collection', array(
'entry_type' => new AnswerType(),
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'empty_data' => null,
))
->add('conditions', 'collection', array(
'entry_type' => new ConditionalType(),
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'empty_data' => null,
))
//->add('children', )
->add('children', 'collection', array(
'entry_type' => new ElementChildType(),
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'empty_data' => null,
))
;
}
ElementChildType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('answers', 'collection', array(
'entry_type' => new AnswerType(),
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'empty_data' => null,
'block_name' => 'child_element_answer',
'prototype_name' => '__child_element_answer__',
))
;
}
This is how the form looks like before i submit it:
(Child element 1 and 2 are already persisted. Child element 3 is a newly added entity in the form
This is the post data:
element[name]:parent-element-name
element[question]: blabla
element[children][0][name]:child element 1
element[children][0][answers][0][name]:child answer 1-1
element[children][1][name]:child element 2
element[children][1][answers][0][name]:child answer 2-1
element[children][2][name]:child element 3
element[children][2][answers][0][name]:child answer 3-1
element[_token]:xxxxxxxxxxxxxxxxxxxxxxxxxxxx
This is how the form looks like after submit:
(As you can see: there is no child answer in child element 3)
And when i add a child answer now (after submitting) the child answer DOES get persisted. The same controller is being called and the same entities, so i am stuck. Please help. Thank you!
EDIT:
I finally edited my controller the following way. Probably not the best practice, but it's working so i thought i should let you know:
/**
* Displays a form to edit an existing Element entity.
*
* #Route("/{id}/edit", name="element_edit")
* #Method({"GET", "POST"})
*/
public function editAction(Request $request, Element $element, $firstPost = NULL)
{
$em = $this->getDoctrine()->getManager();
$deleteForm = $this->createDeleteForm($element);
$editForm = $this->createForm('AppBundle\Form\ElementType', $element);
$editForm->handleRequest($request);
$breadcrumbs = array();
$breadcrumbs[] = array("name" => "Dashboard", "url" => $this->get("router")->generate("dashboard"));
$breadcrumbs[] = array("name" => "Elementen lijst", "url" => $this->get("router")->generate("element_index"));
$breadcrumbs[] = array("name" => "Element bewerken");
if ($editForm->isSubmitted() && $editForm->isValid()) {
$originalChildren = new ArrayCollection();
$postChildren = new ArrayCollection();
$newChildren = new ArrayCollection();
foreach ($element->getChildren() as $child) {
$originalChildAnswers = new ArrayCollection();
$originalChildren->add($child);
foreach ($child->getAnswers() as $childAnswer) {
$originalChildAnswers->add($childAnswer);
}
}
if (isset($request->request->get('element')['children'])) {
foreach ($request->request->get('element')['children'] as $postChild) {
$postChildren->add($postChild);
}
}
$newElements = [];
$i = 0;
foreach ($originalChildren as $child) {
if (null != $child->getId() && $element->getChildren()->contains($child)) {
$i++;
} else {
$newElements[] = $i;
$i++;
}
}
foreach ($newElements as $newElement) {
$newChildren->set($newElement, $postChildren[$newElement]);
}
foreach ($newChildren as $key => $newchild) {
foreach ($newchild['answers'] as $newChildAnswer) {
$childAnswerKey = $element->getChildren()->getKeys()[$key];
$answerEntity = new Answer();
$answerEntity->setName($newChildAnswer['name']);
$element->getChildren()[$childAnswerKey]->addAnswer($answerEntity);
}
}
$em->persist($element);
$em->flush();
return $this->redirectToRoute('element_edit', array('id' => $element->getId()));
}
return $this->render('element/edit.html.twig', array(
'breadcrumbs' => $breadcrumbs,
'element' => $element,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
}

Related

Symfony dependent dropdown with 3 entities

So I'm about to create a form with three dropdowns which are interdependent.
When one or several channel1s are selected, the choices for channel 3 should change, according to what is selected for channel1. And dependent on that, the last dropdown "agencies" should change its choices.
I've already tried different solutions but none of them has worked so far. Right now I'm stuck on the solution provided by Symfony's documentation, which provides code for two entities but even with that one, my second dropdown doesn't have any values, so it's not working.
Here is my Form Type:
class SelectionType extends AbstractType {
protected $tokenStorage;
// private $manager;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
//solution Symfony Documentation
$channel1s = new Channel1();
$currentuser = $this->tokenStorage->getToken()->getUser();
$builder
->add('channel1s', EntityType::class, array(
'class' => 'AppBundle:Channel1',
'property' => 'name',
'label' => 'label.channel1s',
'empty_value' => 'label.select_channel1s',
'mapped' => false,
'expanded' => false,
'translation_domain' => 'UploadProfile',
'multiple' => true,
'required' => false,
));
$formModifier = function (FormInterface $form, Channel1 $channel1s = null) {
$channel3s = null === $channel1s ? array() : $channel1s->getChannel3();
$form->add('channel3s', EntityType::class, array(
'class' => 'AppBundle:Channel3',
'property' => 'name',
'label' => 'label.channel3s',
'empty_value' => 'label.select_channel3s',
'mapped' => false,
'expanded' => false,
'translation_domain' => 'UploadProfile',
'choices' => $channel3s,
'multiple' => true,
'choices_as_values' => true,
));
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
$data = $event->getData();
$formModifier($event->getForm(), $data->getChannel1s());
}
);
$builder->get('channel1s')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$channel1s = $event->getForm()->getData();
$formModifier($event->getForm()->getparent(), $channel1s);
}
);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'DocumentBundle\Entity\UploadProfile'
));
}
public function getName()
{
return 'uploadprofile';
}
}
I've also tried a solution with Subscribers from that page: http://showmethecode.es/php/symfony/symfony2-4-dependent-forms/ but it didn't work out either..
I think my problem is somewhere around that line:
$channel3s = null === $channel1s ? array() : $channel1s->getChannel3();
but that's just a guess..
I also added that ajax function:
var $channel1s = $('#uploadprofile_channel1s');
$channel1s.change(function() {
var $form = $(this).closest('form');
var data = {};
data[$channel1s.attr('name')] = $channel1s.val();
// data[channel3s.attr('name')] = channel3s.val();
$.ajax({
url : $form.attr('action'),
type: $form.attr('method'),
data : data,
success: function(html) {
$('#uploadprofile_channel3s').replaceWith(
$(html).find('#uploadprofile_channel3s')
);
}
});
});
My 3 entities have ManytoMany or OneToMany relationships and I should have all the right getters and setters, but if anyone needs them to varify, let me know and I will upload them!
I've been stuck on this for quite a while now, so I would be happy about any kind of help or advise!
NOTE: there's still a third entity (agency) to be added but since not even the first one's are working, I decided to upload only the first two..
ADDED:
or maybe somebody can explain to my that line:
$channel3s = null === $channel1s ? array() : $channel1s->getChannel3s();
might be, that this is my problem?

Sonata Admin and Dynamic Form

I made several search but I still have a problem...
I want to make a dynamic form. I want to hydrate a select in function of an other select.
This is my configureFormFields:
protected function configureFormFields(FormMapper $formMapper)
{
$emIndustry = $this->modelManager
->getEntityManager('*\*\*\*\Entity\Industry')
;
$query = $emIndustry->getRepository(*:Industry')
->getFindAllParentsQueryBuilder()
;
$formMapper
->add('company')
->add('industry', 'sonata_type_model', [
'attr' => [
'onchange' => 'submit()',
],
'query' => $query,
'required' => false,
])
->add('subIndustry', 'sonata_type_model', [
'choices' => [],
'required' => false,
])
;
$builder = $formMapper->getFormBuilder();
$factory = $builder->getFormFactory();
$subject = $this->getSubject();
$modelManager = $this->getModelManager();
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use($formMapper, $subject, $emIndustry, $modelManager, $factory) {
$form = $event->getForm();
if(!is_null($subject->getIndustry())) {
$query = $emIndustry->getRepository('*:Industry')
->getFindChildrenByParentQueryBuilder($subject->getIndustry())
;
$form
->add(
$factory->createNamed('subIndustry', 'sonata_type_model', null, [
'class' => '*\*\*\*\Entity\Industry',
'query' => $query,
'required' => false,
])
)
;
}
});
}
When I change the value of the select Industry, no problem my form is submited. But nothing happend in second select subIndustry because : all attributes of my $subject object is null...
Have you any idea why ? Is there a best way to make a dynamic form ?
Thank's for your help.
AlexL

How to changes input required attribute in symfony2 form?

I'm using a form on add and edit page in which there is a input file which is required for add record but it is not required on edit page. Is there a way to change attribute on different pages?
$builder->add('title', 'text', array(
'required' => true,
))->add('description', 'textarea', array(
'required' => false,
))->add('fileName', 'file', array(
'data_class' => null,
'required' => true,
'label' => 'Upload File'
));
this is my controller
if ($custFile === null) {
$custFile = new File();
}
$fileForm = $this->createForm(new CustomerFileType(), $custFile);
$fileForm->handleRequest($request);
if ($fileForm->isValid()) {
$data = $fileForm->getData();
$custFile->setUserType('customer');
$custFile->setUserId($request->get('id'));
$custFile->setDateAttached($data->date);
$om->persist($data);
$file = $custFile->getFileName();
if ($file instanceof UploadedFile) {
$uploadManager = $this->get('probus_upload.upload_manager.user_files');
if ($newFileName = $uploadManager->move($file)) {
$custFile->setFileName(basename($newFileName));
}
}
$om->flush();
return $this->redirect($this->generateUrl('minicasp_customer_edit_customer', array(
'id' => $request->get('id'),
)));
}
$fileRecord = array();
if (null !== $customer) {
$fileRecord = $om->createQueryBuilder()
->from('MinicaspCustomerBundle:file', 'f')
->select('f')
->where('f.userId = :customer')
->andWhere('f.userType = :userType')
->orderBy('f.id', 'DESC')
->setParameter('customer', $request->get('id'))
->setParameter('userType', 'customer')
->getQuery()
->getResult()
;
}
return $this->render('MinicaspCustomerBundle:Default:customerAccount.html.twig', array(
'form' => $form->createView(),
'payment_form' => $paymentForm->createView(),
'file_form' => $fileForm->createView(),
'payments' => $payments,
'file_record' => $fileRecord,
'file_edit' => $fileEdit
));
Get your object inside form class and check if your add or edit new record, and based by this set the value of "required" attribute:
$obj = $builder->getData();
$builder->add('fileName', 'file', array(
'data_class' => null,
'required' => $obj->getId() === null ? true : false,
'label' => 'Upload File'
));
#Umair Malik, according to your last comment, you can try this in your controller:
if ($request->getMethod() == 'POST') {
$form->submit($request);
$formData = $form->getData();
if ($formData->getFile() !== null){
/*
* New file has been uploaded
* Save or copy your old record; after this will be over-written
*/
}
$entityManager->persist($formData);
$entityManager->flush();
}
You can create two form types, with different options.
On a side note, you don't need to put "required => true" that's the default option,

Symfony 2: Set field as read only after first save

I have a Symfony 2 entity. When I create a new record, I must fill all the values using a form, but after saving it, one of the values, $amount shouldn't be updatable when I update the others members.
How can I accomplish this? It's possible to mark a form member as a read-only, in runtime?
By using the validation_groups and name options when creating your form, you can change the form.
The name attribute sets the form creation, and the validation_groups takes care of the validation.
For example, in the create/new method of your controller;
public function createAction(Request $request)
{
// Instantiate new Foo object
$client = new Foo();
// create the form (setting validation group)
$form = $this->formFactory->create('foo', $foo, array(
'name' => 'create',
'validation_groups' => array('create')
)
);
// form has been submitted...
if ('POST' === $request->getMethod()) {
// submits the form
$form->handleRequest($request);
// do validation
if ($form->isValid()) {
// do whatever
}
}
// either GET or validation failed, so show the form
return $this->template->renderResponse('FooBundle:foo:add.html.twig', array(
'form' => $form->createView(),
'foo' => $foo
));
}
And in the edit/update function of your controller;
public function updateAction($id, Request $request)
{
// Instantiate Client object
$client = new Foo($id);
// create the form (setting validation group)
$form = $this->formFactory->create('foo', $foo, array(
'name' => 'update',
'validation_groups' => array('update')
));
// form has been submitted...
if ('POST' === $request->getMethod()) {
// submits the form
$form->handleRequest($request);
// do validation
if ($form->isValid()) {
// do whatever
}
}
// either GET or validation failed, so show the form
return $this->template->renderResponse('FooBundle:foo/edit:index.html.twig', array(
'form' => $form->createView(),
'foo' => $foo
));
}
And your Form Type will look something like;
class FooType extends BaseAbstractType
{
protected $options = array(
'data_class' => 'FooBundle\Model\Foo',
'name' => 'foo',
);
private $roleManager;
public function __construct($mergeOptions = null)
{
parent::__construct($mergeOptions);
}
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->$options['name']($builder, $options);
}
private function create(FormBuilderInterface $builder, array $options)
{
// ID
$builder->add('Id', 'text', array(
'required' => true,
'label' => 'ID',
'attr' => array(
'placeholder' => 'Format: 2 alphanumeric (e.g. A1)'
)
));
// Name - only show on create
$builder->add('Name', 'text', array(
'required' => true,
'label' => 'Name',
'attr' => array(
'placeholder' => 'Your name'
)
));
// add the submit form button
$builder->add('save', 'submit', array(
'label' => 'Save'
));
}
private function update(FormBuilderInterface $builder, array $options)
{
// ID
$builder->add('Id', 'text', array(
'required' => true,
'label' => 'ID',
'attr' => array(
'placeholder' => 'Format: 2 alphanumeric (e.g. A1)',
)
));
// Name - just for show
$builder->add('Name', 'text', array(
'required' => true,
'label' => 'Name',
'attr' => array(
'readonly' => 'true' // stops it being editable
)
));
// add the submit form button
$builder->add('save', 'submit', array(
'label' => 'Save'
));
}
}
P.S. All my classes are declared as services, so how you call create forms/views/etc may be different.

Symfony2 ManyToMany multiple field form collection

Is there a way to save correctly in a relationship ManyToMany field entities in a form that is "multiple" and is part of a collection?
I looked everywhere but could not find a shred of example to make me understand how to do!
I cannot find the solution for this!
class Anagrafica
{
/**
* #ORM\ManyToMany(targetEntity="SubCategories", inversedBy="anagrafiche", cascade={"persist", "remove"})
* #ORM\JoinTable(name="AnCat")
**/
private $subCategories;
//..
public function __construct()
{
$this->subCategories = new \Doctrine\Common\Collections\ArrayCollection();
//..
}
/**
* Add subCategories
*
* #param \My\BusinessBundle\Entity\SubCategories $subCategories
* #return Anagrafica
*/
public function addSubCategory(\My\BusinessBundle\Entity\SubCategories $subCategories)
{
foreach ($subCategories as $subCategory) {
$subCategory->addAnagrafiche($this);
}
$this->subCategories = $subCategories;
}
*******
class SubCategories
{
/**
* #ORM\ManyToMany(targetEntity="Anagrafica", mappedBy="subCategories")
*/
private $anagrafiche;
public function __construct()
{
$this->anagrafiche = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add anagrafiche
*
* #param \My\BusinessBundle\Entity\Anagrafica $anagrafiche
* #return SubCategories
*/
public function addAnagrafiche(\My\BusinessBundle\Entity\Anagrafica $anagrafiche)
{
if (!$this->anagrafiche->contains($anagrafiche)) {
$this->anagrafiche->add($anagrafiche);
}
}
******
AnagraficaType:
//..
->add('subCategories', 'collection', array('type' => new SubCategoriesType(),
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'prototype_name' => '__categ__',
'by_reference' => false
))
*******
SubCategoriesType:
->add('subCategory', 'entity', array(
'class' => 'CoffeeBusinessBundle:SubCategories',
'property' => 'subCategory',
'label' => 'Sotto Categorie',
'multiple' => true
))
Partially solved
If I enter the field collection directly and do these changes:
AnagraficaType:
->add('subCategories', 'collection', array(
'type' => 'entity',
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'prototype_name' => '__categ__',
'by_reference' => false,
'options' => array(
'label' => 'Sotto Categorie',
'multiple' => true,
'class' => 'MyBusinessBundle:SubCategories',
'property' => 'subCategory'
)
))
Anagrafica entity:
public function addSubCategory(ArrayCollection $subCategories)
{
foreach ($subCategories as $subCategory) {
$subCategory->addAnagrafiche($this);
//var_dump($subCategory);
}
$this->subCategories = $subCategories;
}
Partially get the desired result, but if I add a field in the form subcategories (by collection) saves only the last field entered.
I did a var_dump ArrayCollection object received and in fact inside there are only data from the last field entered.
Any idea?

Resources