Search code examples
symfonydoctrine-orminsertmany-to-manyjunction-table

Insert all records into junction table in Symfony


I have a next tables: quiz, question, and question_quiz. I have a many-to-many relationship. Insertion work right for table quiz and question, but in table question_quiz inserted only single record.

My tables

"question", it's all right.

+----+--------+
| id | title  |
+----+--------+
| 61 | Title1 |
| 62 | Title2 |
| 63 | Title3 |
+----+--------+

"quiz", it's all right.

+----+-------+--------+
| id | name  | status |
+----+-------+--------+
| 27 | Name1 |      0 |
+----+-------+--------+

"question_quiz"

+-------------+---------+
| question_id | quiz_id |
+-------------+---------+
|          61 |      27 |
+-------------+---------+

In last table must be inserted 61,62,63 quiestion_id, but inserted only single record.

Fragment of my controller.

   $quiz = new Quiz();
   $question = new Question();
   $form = $this->get('form.factory')->createNamed('quiz', QuizType::class, $quiz);
   $quiz->getQuestions()->add($question);
   $question->addQuiz($quiz);
   $form->handleRequest($request);
   if ($form->isSubmitted() && $form->isValid()) {
      $em = $this->getDoctrine()->getManager();
      $em->persist($quiz);
      $em->persist($question);
      $em->flush();
}

The call evidence of a question entity.

  $quiz->getQuestions()->add($question);
  $question->addQuiz($quiz);

I use the collection type, and I can insert any number of questions.

UPDATE QuizType form.

class QuizType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class, array('attr' => ['class' => 'form-control name-quiz'], 'label' => 'Name Quiz'))
            ->add('status', CheckboxType::class, array(
                'label'    => 'Status Quiz',
                'required' => false))
            ->add('save', SubmitType::class, ['label' => 'Add', 'attr' => ['class' => 'btn btn-primary']]);
        $builder
            ->add('questions', CollectionType::class, array(
                'entry_type' => QuestionType::class,
                'entry_options' => array('label' => false),
                'allow_add' => true,
            ));
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Quiz::class
        ]);
    }
}

if I debug the method addQuestion() I got error INSERT INTO question (title) VALUES (?)' with params [null]. But if I dubug method addQuiz I get

   Question {#583 ▼
  -id: null
  -title: "123"
  -quiz: ArrayCollection {#584 ▼
    -elements: array:1 [▼
      0 => Quiz {#581 ▼
        -id: null
        -name: "123"
        -questions: ArrayCollection {#580 ▶}
        -status: false
      }
    ]
  }
  -answers: ArrayCollection {#585 ▶}
}

The debug $form->get('questions')->getData() return

ArrayCollection {#733 ▼
  -elements: array:2 [▼
    0 => Question {#736 ▶}
    1 => Question {#981 ▶}
  ]
}

Solution

  • Without Quiz and Question entities - it is hard to tell. BUT

    I think the problem is in your controller.

     $quiz = new Quiz();
     $question = new Question();
     //...
     $question->addQuiz($quiz);
    

    So you create a new Quiz, which should have multiple questions, right? But you asign a question only once..

    $question->addQuiz($quiz);
    

    so the right way would be like (a bit verbose so you can get the point)

    $quiz = new Quiz();
    //Q 1
    $question_1 = new Question('Title1'); 
    $quiz->addQuestion( $question_1 ); 
    //Q 2
    $question_2 = new Question('Title2'); 
    $quiz->addQuestion( $question_2 );
    //Q 3
    $question_2 = new Question('Title3'); 
    $quiz->addQuestion( $question_3 );
    
    $em->persist($question_1);
    $em->persist($question_2);
    $em->persist($question_2);
    $em->persist($quiz);
    $em->flush();
    

    Take a look at this example (https://gist.github.com/Ocramius/3121916) for ManyToMany relation and especial to addUSerGroup() and addUser() methods.

    Try the suggested method in a controller (without form) and see if it works and if you get more than one rows into question_quiz table

    UPDATE:

    I think the "magic" you're looking for is cascade={"persist"} in your Quiz Entity. Take a look at this article (https://knpuniversity.com/screencast/collections/add-new-collection-cascade-persist). Even though the video isn't free, the text is fine.

    1)set up cascade in yout entity

    2)updated you addQuestion() method in Quiz class like following

    public function addQuestion(Question $question)
    {
      // do not add same questions more than once. 
      if ($this->questions->contains($question)) {
        return;
      }
      $this->questions[] = $question; // to add new question to that quiz
      $question->addQuiz($this); // this quiz now also belongs to a question
    }
    

    3) get embed collection to work with prototype => http://symfony.com/doc/current/form/form_collections.html. So you can add new Questions on the fly

    4)After submit, just call:

    if ($form->isSubmitted() && $form->isValid()) {
       $quiz = $form->getData();
       $em->persist($quiz);
       $em->flush();
    }
    

    4.1)If that doesn't work, try looping over the submitted collection of questions

    if ($form->isSubmitted() && $form->isValid()) {
       $quiz = $form->getData();
       // since getQuestions() gives you an ArrayCollection, isEmpty is a built in method
       if(!$quiz->getQuestions()->isEmpty())
       {
         // this is kinda bad, but you'll have to debug and play around
         foreach ($quiz->getQuestions() as $new_question)
         {
           $em->persist($new_question);
         }
       }
       $em->persist($quiz);
       $em->flush();
    }