Коллекция форм, как избежать создания дублирующейся записи (OneToMany - ManyToOne) - PullRequest
0 голосов
/ 21 апреля 2020

Проблема

У меня есть две сущности, одна из которых называется Question, которая может быть самореферентной, она связана с QuestionSubQuestions (необходимо было добавить несколько дополнительных такие поля, как filter), поэтому у него может быть много вопросов, но во многих Questions могут использоваться те же вопросы, что и у детей. Намерение этого состоит в том, чтобы иметь единую сущность, которая может иметь много Children (Вопросы), и повторно использовать существующие.

Проблема, с которой я сталкиваюсь, заключается в том, что, когда я добавляю существующий Вопрос в качестве дочернего элемента, он создает новую запись Question в базе данных вместо использования существующей записи для ассоциации.

У меня есть веб-интерфейс, где пользователь может выбрать список существующих вопросов и добавить его в качестве ребенка к основному. Форма POST передает всю информацию (включая идентификатор сущности) и doctrine обрабатывает ее самостоятельно.

Все сохраняется и удаляется правильно при добавлении несуществующих вопросов (новых), но при выборе существующего допускает указанную ошибку. Но этого не происходит, когда Вопрос обновляется, doctrine правильно сохраняет существующие отношения и не создаются дублированные записи.

Кроме того, контроллер не содержит ничего особенного, но при выгрузке По данным формы я вижу, что добавленный вопрос не имеет свойства __isInitialized__, поэтому я могу предположить, что doctrine действительно не знает, что эта сущность уже существует. Вы можете видеть в дампе (см. Раздел кода), что у дочернего элемента с индексом 0 есть параметр, а у дочернего с индексом 1.

Вопрос

Итак Как я могу это исправить? Может быть, есть способ проверить, существует ли сущность при обработке данных формы, и снова присоединить сущность к EntityManager? Я знаю, что могу сделать Слушателя для этого, но я не знаю, хорошая ли это практика.

Любая помощь будет оценена.

Фактический код

Дамп данных формы:

Question^ {#1535 ▼
  -id: 56
  -question: "TestB1"
  -children: PersistentCollection^ {#1562 ▼
    -owner: Question^ {#1535}
    -association: array:15 [ …15]
    -em: EntityManager^ {#238 …11}
    -isDirty: true
    #collection: ArrayCollection^ {#1563 ▼
      -elements: array:3 [▼
        0 => QuestionSubQuestion^ {#1559 ▼
          -question: Question^ {#1535}
          -subQuestion: Question^ {#1592 ▼
            +__isInitialized__: true
            -id: "57"
            -question: "P-1"
          }
          -filter: "affirmative"
        }
        1 => QuestionSubQuestion^ {#2858 ▼
          -question: Question^ {#1535}
          -subQuestion: Question^ {#2863 ▼
            -id: "57"
            -question: "P-1"
          }
          -filter: "negative"
        }
      ]
    }
    #initialized: true
  }
}

Question.php

class Question
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    ...

    /**
     * @var ArrayCollection
     * @ORM\OneToMany(targetEntity="QuestionSubQuestion", mappedBy="question", fetch="EAGER" ,cascade={"persist"}, orphanRemoval=true)
     */
    private $children;

    ...

    /**
     * @param QuestionSubQuestion $children
     */
    public function addChild(QuestionSubQuestion $children): void
    {
        if ($this->children->contains($children)) {
            return;
        }

        $children->setQuestion($this);
        $this->children->add($children);
    }

    /**
     * @param mixed $children
     */
    public function removeChild(QuestionSubQuestion $children): void
    {
        if (!$this->children->contains($children)) {
            return;
        }

        $this->children->removeElement($children);
        // needed to update the owning side of the relationship!
        $children->setSubQuestion(null);
    }

}

QuestionSubQuestion.php

class QuestionSubQuestion
{
    /**
     * @ORM\Id
     * @ORM\ManyToOne(targetEntity="Question", inversedBy="children", cascade={"persist"})
     * @ORM\JoinColumn(nullable=false)
     */
    private $question;

    /**
     * @ORM\Id
     * @ORM\ManyToOne(targetEntity="Question", cascade={"persist"})
     * @ORM\JoinColumn(nullable=false)
     */
    private $subQuestion;

    /**
     * @ORM\Id
     * @ORM\Column(type="string")
     * @ORM\JoinColumn(nullable=false)
     */
    private $filter;
}

Форма QuestionType.php

class QuestionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('question')
            ->add('children', CollectionType::class, [
                'entry_type' => SubQuestionEmbeddedForm::class,
                'allow_add' => true,
                'allow_delete' => true,
                'label' => false,
                'by_reference' => false,
                'prototype_name' => '__subQuestion__',
            ]);
    }

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

Встроенная форма SubQuestionEmbeddedForm.php

class SubQuestionEmbeddedForm extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('subQuestion', SubQuestionType::class)
            ->add('filter', HiddenType::class)
        ;
    }

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

SubQuestionType.php

class SubQuestionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('id', HiddenType::class, [
                'required' => false,
            ])->add('question', TextType::class, [
                'label' => false,
            ])
            ->add('country', HiddenType::class)
            ->add('category', HiddenType::class)
        ;
    }

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

Редактировать контроллер

$question = $questionRepository->find($questionId);

$form = $this->createForm(QuestionType::class, $question);
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
    $question = $form->getData();

    $questionRepository->save($question);

    return $this->redirect($request->getUri());
}

1 Ответ

0 голосов
/ 21 апреля 2020

Проверьте это, если проблема была решена: (я добавляю поле заголовка в объекте вопроса, изменяю его в поле идентификации вашего текста или удаляю)

class QuestionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title', TextType::class)

            ->add('children', CollectionType::class, [
                'entry_type' => QuestionSubQuestionType::class,
                'allow_add' => true,
                'allow_delete' => true,
                'label' => false,
                'by_reference' => false,
                'prototype'    => true,
                'prototype_name' => '__subQuestion__',
            ]);
    }

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






class QuestionSubQuestionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('subQuestion', EntityType::class, [
                'label' => false,
                    'class'    => 'YourBundle:Question',
                    'choice_label' => 'title',

                    'multiple' => false,
                    'expanded'  => false,
                    /* use query builder to customize choices
                    'query_builder' => function (MaterialRepository $er) {
                        return $er->getQbOrderBy('m.id', 'DESC');
                     },*/

                ))
             ->add('filter', HiddenType::class)

            /* uncomment if these fields are in SubQuestion Entity
            ->add('country', HiddenType::class)
            ->add('category', HiddenType::class)
            */
        ;
    }

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