Skip to content
Advertisement

Symfony cascade persist or merge duplication

I have a problem with the cascade persist or merge, I have an application with two entities task and table (task_implements)

When I create a task I choose cultures that are linked:

  • If the user chooses 3 cultures => 3 tasks are created
  • If the user chooses 3 cultures then 2 vehicles => 3 tasks must be created and each task 2 vehicles bind

TaskController new()

if ( $form->isSubmitted() && $form->isValid() ) {
        // Cultures
        foreach ( $form->get('cultures')->getData() as $culture) {
            // Set correct status
            if ( $task->getUser() === null ) {
                $task->setStatus( 2 );
            } else {
                $task->setStatus( 3 );
            }
            $task->setCulture( $culture );

            // Implements

            foreach ( $form->get('implements')->getData() as $implement) {
                $taskImplement = new TaskImplement();
                $taskImplement->setImplement( $implement );

                $task->addImplement( $taskImplement );
            }

            $this->em->merge( $task );
            $this->em->flush();
        }


        $this->addFlash('success', 'Nouvelle tache ajoutée avec succès');
        return $this->redirectToRoute('admin_task_index');
    }

TaskNewType

->add('implements', EntityType::class, [
            'class' => Implement::class,
            'query_builder' => function( ImplementRepository $ir ) {
                return $ir->createQueryBuilder('i');
            },
            'choice_label' => function( Implement $implement ) {
                return $implement->getName();
            },
            'mapped' => false,
            'required' => false,
            'expanded' => true,
            'multiple' => true
        ])

TaskEntity

/**
 * @ORMOneToMany(targetEntity=TaskImplement::class, mappedBy="task", orphanRemoval=true, cascade={"persist", "merge"})
 */
private $implements;

public function addImplement(TaskImplement $taskImplement): self
{
    if (!$this->implements->contains($taskImplement)) {
        $this->implements[] = $taskImplement;
        $taskImplement->setTask($this);
    }

    return $this;
}

Desired behavior

If I take example, if I choose 3 cultures application creates 3 tasks and in every task create 2 implement bind

id / culture_id
#1 10
#2 11
#3 12

in task_implement

 id / task_id / implement_id
 #1 1 20
 #2 1 21
 #3 2 20
 #4 2 21
 #5 3 20
 #6 3 21

What I get

     id / culture_id
     #1 10
     #2 11
     #3 12

on task_implement

     id / task_id / implement_id

     #1 | 1 | 20 @
     #2 | 1 | 21 

     #3 | 2 | 20 @
     #4 | 2 | 21 @
     #5 | 2 | 20 @
     #6 | 2 | 21 

     #7 | 3 | 20 @
     #8 | 3 | 21 @
     #9 | 3 | 20 @
     #10 | 3 | 21 @
     #11 | 3 | 20 @
     #12 | 3 | 21

I don’t understand why it doesn’t clear between each record in DB and it duplicates if I put 4 cultures I have 4 entries on the last TaskImplement

Thank you for your help

Advertisement

Answer

Apparently, merge creates a new task entry every time you call it (this probably means, that your $task doesn’t have an id.

On the first cycle of your outer foreach you add 2 TaskImplements, which also don’t have ids. Your task now have 2 TaskImplements (which is what you want at this point).

On the second cycle of your outer foreach you add another 2 TaskImplements – now you have 4 TaskImplements on your task.

Solution options:

  • you either need to stop adding TaskImplements after the first outer foreach (which would be weird, but would work) or
  • you set your task implements instead of adding them or
  • clear the task object of task implements before adding new ones or
  • you “clone” the task before adding task implements (it might be sufficient to just create a new task?).

afterthought

You got a very interesting use of merge. With persist, you should only would have had one Task that contains 6 TaskImplement and only has the last culture set. To be honest, merge without having ids on the task feels like an anti-pattern.

I would assume, that your form itself has data_type Task, which is essentially wrong, since you’re editing/creating multiple tasks. You handle the form as if you have multiple tasks, but you only have one. The root of your problem stems from the wrong semantics …

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement