How to attach an entity with arguments in a collectionType?

I have to manage answers to a form in my Symfony project. I currently have 4 entities :

  • MyForm is representing the global form
  • QuestionsBlock is representing a block of questions
  • Question is representing a question
  • ModelQuestion is representing a question default value

In my MyFormType class, I have a CollectionType attribute of type QuestionsBlockType. Indeed, I would like to dynamically add / remove question blocks in my form. But each question block must contain questions based on model question entities.

For example, if I have 5 questionModel entries in my database, I want 5 new questions created each time I add a question block. As I pass the questions in the QuestionsBlock constructor, I would try to create a new instance of QuestionsBlock with a parameter each time a new entry is created in the CollectionType.

To achieve this, I saw on doc that I could use the prototype_data attribute of the CollectionType : https://symfony.com/doc/current/reference/forms/types/collection.html#prototype-data

With this option, the children types are displayed on screen, but it seems the objects are not created when I submit my form.

So I saw that I could use the empty_data attribute in the QuestionBlockType to create an instance with a parameter : https://symfony.com/doc/current/form/use_empty_data.html

But it seems it is not enough. I know the "empty_data code" is executed when I save the form but I have the error "Too few arguments to function Question::__construct(), 0 passed and exactly 1 expected." So I guess the QuestionsBlock constructor is called at submit but I can't understand why it tries to create a Question instance without args.

What shoud I do ?

Controller

public function addAction(Request $request)
{
    $em = $this->getDoctrine()->getManager();
    $models = $em->getRepository(QuestionModel::class)->findAll();
    $myForm = new MyForm();

    $form = $this->createForm(MyFormType::class, $myForm, ['models' => $models]);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $em->persist($myForm);
        $em->flush();

        return $this->redirectToRoute('...');
    }

    return $this->render('Admin/MyForm/add_edit.html.twig', array(
        'myForm' => $myForm,
        'models' => $models,
        'form'   => $form->createView()
    ));
}

MyForm Entity

/**
 * MyForm
 *
 * @ORM\Table(name="my_form")
 * @ORM\Entity()
 */
class MyForm
{
    /**
     * @ORM\OneToMany(targetEntity="QuestionsBlock", mappedBy="myForm", cascade={"persist", "remove"})
     */
    private $questionsBlocks;

    ...
}

QuestionsBlocks Entity

/**
 * QuestionsBlock
 *
 * @ORM\Table(name="questions_block")
 * @ORM\Entity()
 */
class QuestionsBlock
{
    /**
     * @ORM\OneToMany(targetEntity="question", mappedBy="questionsBlock", cascade={"persist", "remove"})
     */
    private $questions;

    public function __construct($models)
    {
        $this->questions = new ArrayCollection();

        foreach ($models as $model) {
            $this->addQuestion(new Question($model));
        }
    }

    ...
}

Question Entity

/**
 * Question
 *
 * @ORM\Table(name="question")
 * @ORM\Entity()
 */
class Question
{
    /**
     * @ORM\ManyToOne(targetEntity="ModelQuestion")
     */
    private $modelQuestion;

    public function __construct($modelQuestion)
    {
        $this->modelQuestion = $modelQuestion;
    }

    ...
}

MyForm Type

class MyFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $models = $options['models'];

        $builder
            ->add('questionsBlocks', CollectionType::class, array(
                'entry_type'     => QuestionsBlockType::class,
                'required'       => true,
                'allow_add'      => true,
                'allow_delete'   => true,
                'delete_empty'   => true,
                'by_reference'   => false,
                'prototype_data' => (new QuestionBlock($models))
            ))
            ->add('save', SubmitType::class, array(
                'label' => 'Enregistrer'
            ))
        ;
    }

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

QuestionsBlock Type

class QuestionsBlockType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('questions', CollectionType::class, array(
                'entry_type'   => QuestionType::class,
                'required'     => true,
                'allow_add'    => true,
                'allow_delete' => true,
                'delete_empty' => true,
                'by_reference' => false
            ))
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'empty_data' => function (FormInterface $form) {
                return new QuestionsBlock($form->getRoot()->getConfig()->getOption('models'));
            },
            'data_class' => QuestionsBlock::class
        ));
    }
}

Question Type

class QuestionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title', TextType::class, array(
                'label'    => 'Titre',
                'required' => false
            ))
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => Question::class
        ));
    }
}