Reputation: 33
I'm stuck :-)
Maybe I just have the wrong keywords in my research. But I don't get through. So I hope one of the crowd can help me!
I have a unidirectional ManyToMany Association. When I try to submit the form (and therefore persist), I get the error:
A new entity was found through the relationship 'TrainerInnenPool\AppBundle\Entity\Trainer#applicationFields' that was not configured to cascade persist operations for entity: TrainerInnenPool\AppBundle\Entity\ApplicationField@0000000022ef36c600000000087bcbc3.
When I do "cascade persist" a new Entity is created which actually already exists.
I have 2 Entities:
The Trainer has a unidirectional ManyToMany association to the ApplicationField:
class Trainer {
public function __construct()
{
$this->applicationFields = new ArrayCollection();
}
[...]
/**
* @ORM\ManyToMany(targetEntity="ApplicationField")
*/
protected $applicationFields;
The ApplicationField has a self-referencing OneToMany association:
class ApplicationField {
public function __construct() {
$this->children = new ArrayCollection();
}
[...]
/**
* @ORM\OneToMany(targetEntity="ApplicationField", mappedBy="parent")
*/
private $children;
/**
* @ORM\ManyToOne(targetEntity="ApplicationField", inversedBy="children")
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/
private $parent;
I want to create a form where I can add a Trainer - ApplicationField association.
Therefore I have an ApplicationFieldCollectionType:
class ApplicationFieldCollectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('applicationFields', 'collection', array(
'type' => new ApplicationFieldType(),
'allow_add' => true,
'label' => false
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'TrainerInnenPool\AppBundle\Entity\Trainer',
));
}
The embeded Type is as follows:
class ApplicationFieldType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('applicationFieldName', 'entity', array(
'class' => 'TrainerInnenPoolAppBundle:ApplicationField',
'label' => false,
'mapped' => false,
'property' => 'name',
'query_builder' => function(EntityRepository $repository) {
return $repository->createQueryBuilder('application_field')
->where('application_field.parent is NULL');
}
));
$builder->add('name', 'entity', array(
'class' => 'TrainerInnenPoolAppBundle:ApplicationField',
'label' => false,
'property' => 'name',
'query_builder' => function(EntityRepository $repository) {
return $repository->createQueryBuilder('application_field')
->where('application_field.parent = 1');
}
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'TrainerInnenPool\AppBundle\Entity\ApplicationField',
));
}
Last missing part: The controller:
public function editApplicationField($id, Request $request)
{
$entityManager = $this->getDoctrine()->getEntityManager();
$trainer = $entityManager->getRepository('TrainerInnenPoolAppBundle:Trainer')->find($id);
$editForm = $this->createForm(new ApplicationFieldCollectionType(), $trainer);
if ($request->getMethod() == 'POST') {
$editForm->handleRequest($request);
$entityManager->flush();
}
When I fetch the ApplicationField entities from the Trainer and try to persist those,
$editForm->handleRequest($request);
$af = $trainer->getApplicationFields();
foreach ($af as $applicationField) {
$trainer->addApplicationField($applicationField);
$entityManager->persist($applicationField);
}
$entityManager->flush();
I'm not able to do so, since I get a "Duplicate Entry For Key PRIMARY" - Exception.
I think I terribly miss any obvious point. If someone could help me, gave me a hint or just mentioned to update my question with information, I would be so thankful.
Kind regards...
Upvotes: 0
Views: 746
Reputation: 856
Your nested type setters are not called because of a missing property:
->add('applicationFields', 'collection', array(
'type' => new ApplicationFieldType(),
...
'by_reference' => false
...
http://symfony.com/doc/current/reference/forms/types/collection.html#by-reference
When you plan to have collection fields that are addable/deletable("allow_add", "allow_delete"), you should always provide the "by_reference" => false option, in order to call the setters directly on the related entities and then build the association, rather than chain methods from the original entity.
Hope this helps!
Upvotes: 1
Reputation: 954
Well you need the cascade={"persist"} annotation because you want to persist the whole entity with its association to ApplicationField. If you don't use cascade={"persist"}, you'd have to manually persist the entities.
The entities are already added to the trainer, so if you want to manually persist the entities you should remove the line
$trainer->addApplicationField($applicationField);
and only execute the persist.
This should work. Try it. But I think the effect will be the same as if you use cascade persist. So it's not the final solution I think, but the first step to understand the problem, why the manual persist didn't work before.
Upvotes: 0