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 …