I'm trying to create Symfony form for quiz, that has questions and choices for each question (see entities code). I have RequestQuestion
entity that contains description, isActive, choices
. Choices are another entity that contains name, correct
and relation
to question. And finally Request
contains ManyToMany choices (which represents, that the user ticked this choice).
But now I have problem, that I need to somehow in the form group choices by questions (Using EntityType with multiple and expanded true). And no - EntityType's group_by does not work for multiple = expanded = true. That works only for selection box.
Later I added to Request
relation to Question
. That solved half of the problem - I could now in FormType add CollectionType to questions (which is another FormType RequestQuestionType
). Note that this creates redudancy in DB which is not good. (Actually it does not. I wouldn't have information which questions were used for this request as the questions can change in time by setting isActive
or adding new questions). But the problem now is in RequestQuestionType
I can't add answer as the question does not have this relation (that has only Request
or QuestionChoice
).
The question is how can I get the answers into this form? I can't use parrent (RequestFormType
) as I couldn't group the choices by questions and in questions (RequestQuestionType
) I can't add the relation. Bellow I'm sending current state of the code.
Request
/**
* @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;
RequestQuestion
/**
* @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' => []
]);
}
}
Note: for some reason the questions from RequestFormType
does not pass as data (data = null) so that's why I pass them as entry_options. But in the RequestQuestionType
it calls it as many times as the questions count is, so that's little bit weird, but I managed to work around it by the entry_otpions and use index from propertyPath.
Note: the request is prebuild with dummy data - questions and passed to this form.
Note: I also tried before to decompose manyToMany relation in Request - RequestChoice as RequestAnwer with bool as the user ticked or not the choice and pregenerate all answer to questionChoices. But the problem with grouping the choices by question was there too so I couldn't manage to get it work either.
Solved.
I've added RequestQuestionAnswers
, that has OneToMany to RequestQuestion
(RequestQuestionAnswers
has one question) and answers as ManyToMany to RequestQuestionChoice
. That way this new entity is binded 1:1 to question and for each question I can generate EntityType for each question individually and generate question's choices.
If someone will have similar problem I'm pasting here final codes. Also this question was very helpfull: Create quizz form symfony
NOTE: sadly I can't use this for multiple false as the RequestQuestion.requestAnswers
is Collection so it throws error: 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
]);
}
}