mike
mike

Reputation: 33

Symfony2 Unidirectional ManyToMany Collection Form

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

Answers (2)

pcm
pcm

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

Tobias
Tobias

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

Related Questions