【问题标题】:Form Collection how to avoid creating duplicate record (OneToMany - ManyToOne)表单集合如何避免创建重复记录(OneToMany - ManyToOne)
【发布时间】:2020-04-21 11:48:24
【问题描述】:

问题

我有两个实体,一个叫Question,可以自引用,它与QuestionSubQuestions有关联(有必要添加一些额外的字段,如filter),所以它可以有很多问题,但是同样的问题可以在许多Questions 中用作儿童。它的目的是拥有一个可以有许多Children(问题)并重新使用现有的实体。

我面临的问题是,当我将现有问题添加为孩子时,它会在数据库中创建一个新的 Question 记录,而不是使用现有的关联记录。

我有一个网络界面,用户可以在其中从现有问题列表中选择并将其作为子问题添加到主要问题中。表单 POST 所有信息(包括实体的 id)和学说自行处理。

添加不存在的问题(新问题)时,所有内容都会正确保存和删除,但在选择现有问题时会出现上述错误。但是当问题更新时,这不会发生,学说正确地保留现有关系并且不会出现创建重复记录。

此外,控制器不包含任何特殊内容,但是在转储表单的数据时,我可以看到添加的问题没有 __isInitialized__ 属性,所以我猜教义并不真正知道那个实体已经存在。您可以在转储中看到(参见代码部分)索引为 0 的子节点具有参数,而索引为 1 的子节点没有。

问题

那么,我该如何解决这个问题?也许有没有办法在处理表单数据时检查实体是否存在并将实体再次附加到EntityManager?我知道我可以为此制作一个 Listener,但我不知道这是否是一个好习惯。

任何帮助将不胜感激。

实际代码

表单数据转储:

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());
}

【问题讨论】:

  • 我不确定是否理解,但我认为您想创建一个问题树。您不需要 SubQuestion 类,并且 Question 的孩子也是 Question 。我是对的?
  • @threeside 是的,SubQuestion 不是一个现有的类,但它是同一个 Question 类,可以有许多 Question 孩子。其背后的原因是在关系模型中添加了一些额外的字段(不仅仅是 id)。由于您无法使用额外的字段创建多对多,因此我必须创建一个单对多(问题->问题子问题)和多对一(问题子问题->问题)。整个表单的那部分工作正常,问题是在将现有问题添加为子问题时,我想教条选择已发布问题的 id 并链接两个实体,而不是创建新的问题记录。
  • 好的,我现在明白了。您没有显示您的 SubQuestionType,您是否有可能直接在 SubQuestionType 中将新问题添加为子问题或仅使用现有问题?
  • @threeside 抱歉,我刚刚添加了表单类型。是的,在界面中我有一个使用children 字段的HTML 原型的按钮,因此我可以添加一个具有新唯一数据的新子项。但是,我也有一个用 twig 制作的选择列表,在其中我选择我想要的问题的 id,并使用 Javascript 手动使用来自另一个按钮的原型将问题添加到集合中,然后将数据传递给输入,甚至是 id .然后我屏蔽这些字段以避免数据操作,因为它应该包含与原始问题相同的数据。

标签: php symfony doctrine-orm symfony4 formcollection


【解决方案1】:

如果问题已解决,请测试:(我在问题实体中添加标题字段,通过您的文本标识字段更改或删除)

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,
        ));
    }
}

【讨论】:

  • 我认为最好只使用 subQuestionFormType 中的现有结果。在您的情况下,我使用 $ajax 请求添加新问题并将其添加到 subQuestionType 列表中。
  • 感谢您的宝贵时间,我会尝试您的解决方案。
猜你喜欢
  • 2012-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-16
  • 1970-01-01
  • 2017-09-27
  • 2019-10-15
相关资源
最近更新 更多