Here is the situation, I have a form in which I need an entity field type. Inside the BenefitGroup entity I have a BenefitGroupCategory selection.
My buildform is:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('BenefitGroupCategories', 'entity', array(
'class' => 'AppBundle:BenefitGroupCategory',
'property' => 'name',
'label' => false,
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('c')
->orderBy('c.name', 'ASC');
},))
->add('benefitsubitems', 'collection', array('type' => new BenefitSubItemFormType(), 'allow_add' => true, 'label' => false,));
}
It's almost a typical product-category relationship. A BenefitGroup can have only one category and a category can belong to many BenefitGroups (the only complication, not implemented yet, but that's the reason I need the query builder, is that all will depend on another parameter (project) so that some categories will be the default ones (always available), others will be available only for specific projects (see below the reference to project in the BenefitGroupCategory entity)).
You'll notice another field, benefitsubitems, which is not relevant for the question at hand.
As far I understand it, from the Doctrine perspective, I have to set up a One-To-Many, Unidirectional with Join Table.
The two entities are:
<?php
// src/AppBundle/Entity/BenefitGroup.php
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="AppBundle\Entity\BenefitGroupRepository")
* @ORM\Table(name="benefit_groups")
*/
class BenefitGroup
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\ManyToOne(targetEntity="BenefitItem", cascade={"persist"}, inversedBy="BenefitGroups")
*/
protected $benefitItem;
/**
* @ORM\oneToMany(targetEntity="BenefitSubItem", mappedBy="benefitGroup")
*/
protected $BenefitSubItems;
/**
* @ORM\ManyToMany(targetEntity="BenefitGroupCategory")
* @ORM\JoinTable(name="BenefitGroup_BenefitGroupCategory", joinColumns={@ORM\JoinColumn(name="BenefitGroup_id", referencedColumnName="id")}, inverseJoinColumns={@ORM\JoinColumn(name="BenefitGroupCategory_id", referencedColumnName="id", unique=true)})
*/
protected $BenefitGroupCategories;
// HERE I HAVE SOME IRRELEVANT GETTERS AND SETTERS
/**
* Constructor
*/
public function __construct()
{
$this->BenefitSubItems = new ArrayCollection();
$this->BenefitGroupCategories = new ArrayCollection();
}
/**
* Add BenefitGroupCategories
*
* @param \AppBundle\Entity\BenefitGroupCategory $benefitGroupCategories
* @return BenefitGroup
*/
public function addBenefitGroupCategory(\AppBundle\Entity\BenefitGroupCategory $benefitGroupCategories)
{
$this->BenefitGroupCategories[] = $benefitGroupCategories;
return $this;
}
/**
* Remove BenefitGroupCategories
*
* @param \AppBundle\Entity\BenefitGroupCategory $benefitGroupCategories
*/
public function removeBenefitGroupCategory(\AppBundle\Entity\BenefitGroupCategory $benefitGroupCategories)
{
$this->BenefitGroupCategories->removeElement($benefitGroupCategories);
}
/**
* Get BenefitGroupCategories
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getBenefitGroupCategories()
{
return $this->BenefitGroupCategories;
}
}
You'll also notice another entity, BenefitItem, which is the "father" of BenefitGroup.
And
<?php
// src/AppBundle/Entity/BenefitGroupCategory.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* @ORM\Entity()
* @ORM\Table(name="benefit_group_category")
* @UniqueEntity(fields={"name", "project"}, ignoreNull=false, message="Duplicated group category for this project")
*/
class BenefitGroupCategory
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string", length=50)
*/
protected $name;
/**
* @ORM\ManyToOne(targetEntity="Project")
*/
protected $project;
// HERE I HAVE SOME IRRELEVANT GETTERS AND SETTERS
}
In the controller (you'll see several embedded collections, which work ok) I have:
/**
* @Route("/benefit/show/{projectID}", name="benefit_show")
*/
public function showAction(Request $request, $projectID)
{
$id=4; //the Id of the CVC to look for
$storedCVC = $this->getDoctrine()
->getRepository('AppBundle:CVC')
->find($id);
$form = $this->createForm(new CVCFormType(), clone $storedCVC);
$form->handleRequest($request);
if ($form->isValid())
{
$em = $this->getDoctrine()->getManager();
//$benefitGroupCategoryRepository = $this->getDoctrine()->getRepository('AppBundle:BenefitGroupCategory');
$formCVC = $form->getData();
$em->persist($formCVC);
foreach ($formCVC->getBenefitItems() as $formBI)
{
$newBI = new BenefitItem();
$newBI->setCVC($formCVC);
$newBI->setComment($formBI->getComment());
$em->persist($newBI);
foreach ($formBI->getBenefitGroups() as $formBG)
{
$newBG = new BenefitGroup();
$newBG->setBenefitItem($newBI);
$newBG->setBenefitGroupCategories($formBG->getBenefitGroupCategories());
$em->persist($newBG);
foreach ($formBG->getBenefitSubItems() as $formSI)
{
$newSI = new BenefitSubItem();
$newSI->setBenefitGroup($newBG);
$newSI->setComment($formSI->getComment());
$em->persist($newSI);
}
}
}
$em->flush();
}
return $this->render('benefit/show.html.twig', array(
'form' => $form->createView(),
));
}
The problem is: in visualization it visualizes correctly the form (even though it does not retrieve correctly the category. I have a choice of categories, which is ok, but it does not retrieve the right one. Do I have to set the default value in the form?
The problem gets way worse when I sumbit the form it's supposed to create a new entity (notice the clone) with all the nested ones. The problem is that it crashes saying:
Neither the property "BenefitGroupCategories" nor one of the methods
"addBenefitGroupCategory()"/"removeBenefitGroupCategory()",
"setBenefitGroupCategories()", "benefitGroupCategories()", "__set()" or
"__call()" exist and have public access in class
"AppBundle\Entity\BenefitGroup".
The "beauty" is that even if I comment completeley the (nasty) part inside the "isValid" it behaves exactly the same.
I'm lost :(