Я пытаюсь создать форму Symfony для викторины, в которой есть вопросы и варианты выбора для каждого вопроса (см. код сущностей). У меня есть сущность RequestQuestion
, которая содержит description, isActive, choices
. Выбор — это еще одна сущность, которая содержит name, correct
и relation
вопрос. И, наконец, Request
содержит варианты выбора ManyToMany (что означает, что пользователь отметил этот выбор).
Но теперь у меня проблема, что мне нужно как-то в форме сгруппировать варианты по вопросам (используя EntityType с множественным и расширенным значением true). И нет - group_by EntityType не работает для множественного = расширенного = истинного. Это работает только для поля выбора.
Позже я добавил к Request
отношение к Question
. Это решило половину проблемы — теперь я мог в FormType добавить к вопросам CollectionType (это еще один FormType RequestQuestionType
). Обратите внимание, что это создает избыточность в БД, что не очень хорошо. (На самом деле нет. У меня не было бы информации, какие вопросы использовались для этого запроса, так как вопросы могут меняться во времени, устанавливая isActive
или добавляя новые вопросы). Но проблема теперь в RequestQuestionType
Я не могу добавить ответ, так как вопрос не имеет этого отношения (есть только Request
или QuestionChoice
).
Вопрос в том, как я могу получить ответы в этой форме? Я не могу использовать родитель (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;
ЗапросВопросВыбор
/**
* @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;
Тип ЗапросаФормы
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
]);
}
}
ЗапросВопросТип
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.
Примечание: запрос предварительно построен с фиктивными данными - вопросами и передан в эту форму.
Примечание. Я также пытался раньше разложить отношение manyToMany в Request - RequestChoice как RequestAnwer с bool, поскольку пользователь отметил или нет выбор, и предварительно сгенерировать все ответы на questionChoices. Но проблема с группировкой вариантов по вопросам тоже была, поэтому мне тоже не удалось заставить ее работать.
Решено.
Я добавил RequestQuestionAnswers
, который имеет OneToMany к RequestQuestion
(у RequestQuestionAnswers
есть один вопрос) и отвечает как ManyToMany к RequestQuestionChoice
. Таким образом, эта новая сущность привязывается 1:1 к вопросу, и для каждого вопроса я могу генерировать EntityType для каждого вопроса отдельно и генерировать варианты вопросов.
Если у кого-то будет похожая проблема, я вставляю сюда окончательные коды. Также этот вопрос был очень полезен: Создать форму викторины symfony
ПРИМЕЧАНИЕ: к сожалению, я не могу использовать это для множественного false, так как RequestQuestion.requestAnswers
является коллекцией, поэтому выдает ошибку: 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?
Тип ЗапросаФормы
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
]);
}
}
ЗапросВопросТип
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
]);
}
}