Symfony 表单 - 有选择的问题(测验)

Symfony Form - questions (quiz) with choices

我正在尝试为测验创建 Symfony 表单,其中包含问题和每个问题的选项(请参阅实体代码)。我有 RequestQuestion 个包含 description, isActive, choices 的实体。 Choices 是另一个包含 name, correctrelation 的实体来提问。最后 Request 包含 ManyToMany 选项(表示用户勾选了此选项)。

但现在我遇到了问题,我需要以某种方式在表单中按问题分组选择(将 EntityType 与 multiple 和 expanded true 结合使用)。不 - EntityType 的 group_by 不适用于 multiple = expanded = true。这仅适用于选择框。

后来我添加了 RequestQuestion 的关系。这解决了一半的问题——我现在可以在 FormType 中将 CollectionType 添加到问题中(这是另一个 FormType RequestQuestionType)。 请注意,这会在数据库中产生冗余,这并不好。(实际上并不会。我不知道该请求使用了哪些问题,因为问题可以通过设置及时更改isActive 或添加新问题)。但现在的问题是 RequestQuestionType 我无法添加答案,因为问题没有这种关系(只有 RequestQuestionChoice)。

问题是我怎样才能得到这个表格的答案?我不能使用 parrent (RequestFormType),因为我无法按问题对选项进行分组,而在问题 (RequestQuestionType) 中,我无法添加关系。下面我发送代码的当前状态。

请求

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

    /**
     * @ORM\Column(type="uuid")
     */
    private $uuid;

    /**
     * @ORM\ManyToOne(targetEntity=User::class, inversedBy="requests")
     * @ORM\JoinColumn(nullable=false)
     */
    private $User;

    /**
     * @ORM\Column(type="datetime")
     */
    private $created;

    /**
     * @ORM\Column(type="datetime", nullable=true)
     */
    private $resolved;

    /**
     * @ORM\ManyToOne(targetEntity=User::class)
     */
    private $resolvedBy;

    /**
     * @ORM\Column(type="string", length=32)
     */
    private $state;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $address;

    /**
     * @ORM\ManyToMany(targetEntity=RequestQuestion::class, inversedBy="requests")
     */
    private $questions;

    /**
     * @ORM\ManyToMany(targetEntity=RequestQuestionChoice::class, inversedBy="scholarRequestsAnswers")
     */
    private $answers;

请求问题

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

    /**
     * @ORM\Column(type="text")
     */
    private $description;

    /**
     * @ORM\Column(type="boolean")
     */
    private $isActive;

    /**
     * @ORM\OneToMany(targetEntity=RequestQuestionChoice::class, mappedBy="Question", orphanRemoval=true)
     */
    private $choices;

    /**
     * @ORM\ManyToMany(targetEntity=Request::class, mappedBy="questions")
     */
    private $requests;

RequestQuestionChoice

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

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @ORM\Column(type="boolean")
     */
    private $correct;

    /**
     * @ORM\ManyToOne(targetEntity=RequestQuestion::class, inversedBy="choices")
     * @ORM\JoinColumn(nullable=false)
     */
    private $Question;

RequestFormType

class RequestFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('address', TextType::class, [
                'constraints' => [
                    new NotBlank([
                        'message' => "Zadejte adresu"
                    ])
                ]
            ])
            ->add('questions', CollectionType::class, [
                'entry_type' => RequestQuestionType::class,
                'entry_options' => [
                    'questions' => $builder->getData()->getQuestions()
                ]
            ])
            ->add('tos', CheckboxType::class, [
                'mapped' => false,
                'value' => false,
                'constraints' => [
                    new IsTrue([
                        'message' => "Musíte souhlasit s našimi podmínkami použití"
                    ])
                ]
            ])
            ->add('Submit', SubmitType::class)
        ;
    }

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

RequestQuestionType

class RequestQuestionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $index = str_replace(["[", "]"], "", $builder->getPropertyPath());

        $builder
            ->add('???', EntityType::class, [
                'class' => RequestQuestionChoice::class,
                'choice_label' => 'name',
                'choices' => $options["questions"][$index]->getChoices(),
                'expanded' => true,
                'multiple' => true
            ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'questions' => []
        ]);
    }
}

注意:出于某种原因,来自 RequestFormType 的问题没有作为数据传递(data = null),所以我将它们作为 entry_options 传递。但是在 RequestQuestionType 中它调用它的次数与问题计数一样多,所以这有点奇怪,但我设法通过 entry_otpions 解决了它并使用了 propertyPath.[=31= 中的索引]

注意:请求是使用虚拟数据预先构建的 - 问题并传递给此表单。

注意:我之前也尝试过将 Request - RequestChoice 中的 manyToMany 关系分解为 RequestAnwer 和 bool,因为用户是否勾选了选择并预生成对 questionChoices 的所有答案。但是按问题对选项进行分组也存在问题,所以我也无法让它工作。

已解决。

我已经添加了 RequestQuestionAnswers,其中有一对多到 RequestQuestionRequestQuestionAnswers 有一个问题)并且答案是多对多到 RequestQuestionChoice。这样,这个新实体就绑定 1:1 到问题,对于每个问题,我可以单独为每个问题生成 EntityType 并生成问题的选择。

如果有人遇到类似问题,我将在此处粘贴最终代码。这个问题也很有帮助:

注意:遗憾的是我不能将它用于多个 false,因为 RequestQuestion.requestAnswers 是 Collection 所以它会抛出错误:Entity of type "Doctrine\Common\Collections\ArrayCollection" passed to the choice field must be managed. Maybe you forget to persist it in the entity manager?

RequestFormType

class RequestFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('address', TextType::class, [
                'constraints' => [
                    new NotBlank([
                        'message' => "Zadejte adresu"
                    ])
                ],
            ])
            ->add('requestAnswers', CollectionType::class, [
                'entry_type' => RequestQuestionType::class,
                'entry_options' => [
                    'request' => $builder->getData(),
                    'label_attr' => [
                        'class' => 'd-none'
                    ]
                ],
                'label' => 'Dotazník'
            ])
            ->add('tos', CheckboxType::class, [
                'mapped' => false,
                'value' => false,
                'constraints' => [
                    new IsTrue([
                        'message' => "Musíte souhlasit s našimi podmínkami použití"
                    ])
                ],
                'label' => 'Souhlasím s podmínkami použití'
            ])
            ->add('Submit', SubmitType::class, [
                'label' => 'Odeslat'
            ])
        ;
    }

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

RequestQuestionType

class RequestQuestionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $index = str_replace(["[", "]"], "", $builder->getPropertyPath());
        /** @var RequestAnswers $answers */
        $answers = $options["request"]->getRequestAnswers()[$index];

        $builder
            ->add('selectedChoices', EntityType::class, [
                'class' => RequestQuestionChoice::class,
                'choices' => $answers->getQuestion()->getChoices(),
                'choice_label' => 'name',
                'label' => $answers->getQuestion()->getDescription(),
                'expanded' => true,
                'multiple' => true
            ]);
    }

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