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
]);
}
}
Related
I am trying build a form using symfony2 form CollectionType With onetoMany relationship in between two entities. But it always end with Invalid argument exception Could not load type "Symfony\Component\Form\Extension\Core\Type\CollectionType".
I have Two entities called Ticket and Attachment. A ticket has many attachments and each attachment relates to single ticket.
In Tickets Entity :
/**
* #ORM\OneToMany(targetEntity="Attachment", mappedBy="ticket", fetch="EXTRA_LAZY", cascade={"persist"})
*/
private $attachments;
And in Attachment Entity :
/**
* #var integer
* #ORM\ManyToOne(targetEntity="Ticket", inversedBy="attachments", fetch="LAZY")
* #ORM\JoinColumn(name="ticket_id", referencedColumnName="id")
*/
private $ticket;
AttachmentType Form :
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('file')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Attachment'
));
}
TicketType Form :
public function buildForm(FormBuilderInterface $formBuilderInterface, array $options){
$formBuilderInterface
->add("subject", "text", array(
"label"=>"RaiseTicketType.labels.subject.label",
"attr" => array(
"class" => "ui-flat",
"placeholder"=>"RaiseTicketType.labels.subject.label"
)
)
)
->add("attachments", CollectionType::class, array(
'entry_type' => AttachmentType::class,
'allow_add' => true,
'allow_delete' => true
)
);
;
}
public function setDefaultOptions(OptionsResolverInterface $optionsResolverInterface){
$optionsResolverInterface->setDefaults(array(
'data_class' => 'Ticket'
));
}
I have already import the CollectionType namespace
The above implementation is for the latest version of the symfony (3.0) and here is implementation for the symfony version older than 3.0. Just need to replace the attachment field with
->add("attachments", "collection", array(
'type' => new AttachmentType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
)
);
I use the A2lix Translation Form Bundle to realize database translation of entities. I create a entity page, which look like this:
class Page
{
/**
* Must define for translating this entity
*/
use ORMBehaviors\Translatable\Translatable;
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
}
I create also a "PageTranslation" entity:
class PageTranslation
{
use ORMBehaviors\Translatable\Translation;
/**
* #ORM\Column(type="string", length=25)
*/
protected $title;
}
In the form "PageType" i include the translations:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('translations', 'a2lix_translations', array(
'fields' => array(
'title' => array(
'label' => 'pageTitle',
'field_type' => 'text',
'attr' => array()
)
)
));
}
/**
* Define mapper for this form
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'App\MyBundle\Entity\Page',
'cascade_validation' => true,
));
}
I would like to validate the title attribute which is defined in the "PageTranslation" entity.
App\MyBundle\Entity\Page:
properties:
translations:
- Valid: ~
App\MyBundle\Entity\PageTranslation:
properties:
title:
- NotBlank: ~
- Length:
max: 255
But if i send the form with a empty title, the validation return as value "TRUE". Can someone give me a hint for my issue?
I try to save values of one - three checkboxes in field category in database, but i get the error :
Notice: Array to string conversion in /var/www/OnTheWay/vendor/doctrine/dbal/lib/Doctrine/DBAL/Statement.php line 120
The field:
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $category;
Get & Set:
/**
* #return mixed
*/
public function getCategory()
{
return $this->category;
}
/**
* #param $category
*/
public function setCategory($category)
{
$this->category[] = $category;
}
Profile type:
namespace Vputi\UserBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ProfileType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('fio');
$builder->add('birthDate', null, array('widget' => 'single_text'));
$builder->add('file');
$builder->add('yearOnRoad');
$builder->add('telephone');
$builder->add('contactMail');
$builder->add('role', 'choice', array('choices' => array(1 => 'За рулем') ,'expanded'=>true, 'multiple' => true,));
$builder->add('category', 'choice', array(
'choices' => array('A' => 'Категория А', 'B' => 'Категория B', 'C' => 'Категория C',),
'expanded' => true,
'multiple' => true,
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' =>'Vputi\UserBundle\Entity\Profile',
'cascade_validation' => true,
));
}
}
Here is my form type, I hope you help me, and iam ommit getName() method.
The problem is $category is defined as a string but you're using it like an array.
The solution depends on exactly what you want to accomplish. If you want it to be mapped as an array you have to do this:
/**
* #ORM\Column(type="array", nullable=true)
*/
private $category;
When using Doctrine's array type, make sure you take this into account: How to force Doctrine to update array type fields?
Hello everybody (please excuse my English).
I want to do an application which needs to allow that the users must fill out on a form their personal data, their children, grandchildren and great-grandchildren (a little family tree).
class Person
{
/**
* #var int
*
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var string
*
* #ORM\Column(type="string")
*/
private $firstname;
/**
* #var string
*
* #ORM\Column(type="string")
*/
private $lastname;
/**
* #var \DateTime
*
* #ORM\Column(type="datetime")
*/
private $dateOfBirth;
/**
* #var Person
*
* #ORM\ManyToMany(targetEntity="Person")
*/
private $children;
public function __construct()
{
$this->children = new ArrayCollection();
}
}
}
In the PersonType class, I do the following:
class PersonType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('firstname');
$builder->add('lastname');
$builder->add('dateOfBirth');
$builder->add('children', 'collection', array(
'type' => new PersonType(),
'allow_add' => true,
'by_reference' => false,)
);
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Anything\YourBundle\Entity\Person'
));
}
/**
* #return string
*/
public function getName()
{
return 'person';
}
}
In this way, I use the PersonType in the controller as below:
public function newAction()
{
$entity = new Person();
$form = $this->createForm(new PersonType(), $entity, array(
'action' => $this->generateUrl('person_create'),
'method' => 'POST',
));
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
But the problem is when I request the url of this action, and the view of this action has to be rendered, there is a problem because doesn't give a response, because is in a infinite loop (I think that is the reason). I would like to know if is this possible to do using the Symfony forms, or if I have to look at other alternatives. If this was possible, how could I do that and how could I limit the form to only render the four levels that I need (me, my children, my grandchildren and my great-grandchildren)??
I hope that the problem has been understood.
Thanks in advance.
You could add a custom parameter to your form that indicates the current level of recursion.
To archive this you first need to implement a new option:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Anything\YourBundle\Entity\Person',
'recursionLevel' => 4
));
}
Now you update this value in your buildForm method:
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
if (--$options['recursionLevel'] > 0) {
$resolver = new OptionsResolver();
$resolver->setDefaults(
$options
);
$childType = new PersonType();
$childType->setDefaultOptions($resolver);
$builder->add('children', 'collection', array(
'type' => $childType,
'allow_add' => true,
'by_reference' => false
));
}
}
This is not tested.
I had the same problem and tried the solutions provided here.
They come with significant drawbacks like a depth limitation and performance overhead - you always create form objects even if there is no data submited.
What I did to overcome this problem was to add a listener for the FormEvents::PRE_SUBMIT event and add the collection type field dynamically if there is data to be parsed.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('content');
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$node = $event->getData();
$form = $event->getForm();
if (!$node) {
return;
}
if(sizeof(#$node['children'])){
$form->add('children', CollectionType::class,
array(
'entry_type' => NodeType::class,
'allow_add' => true,
'allow_delete' => true
));
}
});
}
I hope this helps someone that has this issue in the future
Thanks for the answer Ferdynator!!
I didn't solve the problem in the way you proposed, but that approach helped me. I passed the recursion level in the constructor of the Person form, and thus, I could know when I had to stop:
class PersonType extends AbstractType
{
private $recursionLevel;
public function __construct( $recursionLevel ){
$this->recursionLevel = $recursionLevel;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
if($this->recursionLevel > 0)
{
$builder->add('children', 'collection', array(
'type' => new PersonType(--$this->recursionLevel),
'allow_add' => true,
'by_reference' => false,)
);
}
}
}
Ferdynator, thanks for your answers. And I want to propose my decision based on yours:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Anything\YourBundle\Entity\Person',
'recursionLevel' => 4
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
if (--$options['recursionLevel'] > 0) {
$builder->add('children', 'collection', array(
'type' => $childType,
'allow_add' => true,
'by_reference' => false,
'options' => [
'recursionLevel' => $options['recursionLevel']
],
));
}
}
It solves our problem.
I have this form with manyTomany relation working perfectly like this:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('manifestations', 'entity', array(
'class' => 'PrifProtocoleBundle:Manifestation',
'multiple' => true,
'expanded' => false,
'property' => 'libelle',
'empty_value' => 'Choississez',
'required' => false,));
}
but i want to set the'multiple' parameter to 'false', this way, i just have a select box with the option 'Choississez', so when i click on it, it displays all the other values. Unfortunately i get an error message: nor of the methods _set()" or "_call()" exist and have public access in class. i've been searching for some solutions on the web and tried this one:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('manifestations', 'collection', array(
'type' => 'entity',
'options' => array(
'class' => 'AcmeProtoBundle:Manifestation',
'multiple' => false,
'expanded' => false,
'property' => 'libelle',
'empty_value' => 'Choisissez',
'required' => false,)));
}
i have no error message! but the select form doesn't display even when i set the 'multiple' to 'true, i only have the submit button, when clicked shows me the results, so i think i miss something in the parameters to display the form!
can anyone help? Thanks
Manifestation.php
/**
* #ORM\Entity
* #ORM\Entity(repositoryClass="ManifestationRepository")
*/
class Manifestation {
public function __construct() {
$this->dateCreation = new \DateTime;
$this->dateModif = new \DateTime;
}
public function __toString() {
return $this->getLibelle();
}
/**
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Id
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="integer")
* #Assert\GreaterThan(
* value = 0,
* message = "La valeur doit être positive"
* )
*/
private $numOrdre;
/**
* #ORM\Column(type="string",length=50)
* #Assert\Length(
* min = "5",
* minMessage = "Le libellé doit faire au moins {{ limit }} caractères"
* )
*/
private $libelle;
/**
* #ORM\Column(type="datetime")
*/
private $dateCreation;
/**
* #ORM\Column(type="datetime")
*/
private $dateModif;
/**
* #ORM\Column(type="boolean")
* #Assert\NotBlank( message=" ")
*/
private $etat;
//getters and setters
invite.php
/**
* #ORM\Entity
* #ORM\Entity(repositoryClass="InviteRepository")
*
*/
class Invite {
/**
* #var boolean
*
* #ORM\ManyToMany(targetEntity="Acme\ProtoBundle\Entity\Manifestation", cascade={"persist"})
* #Assert\NotBlank(message=" ")
*/
private $manifestations;
Can you show your manifestation.php file (your entity)?
Multiple doesn't have the behaviour you are looking for : Multiple is used to allow a user to check multiple checkboxes (true) or only one (false) of a given form (symfony doc : multiple).
In your case a common solution is to use javascript on a parent field that would disable/enable the children fields. Make sure to add server-side validation on these fields if you go for this.
This is the solution working for me now with 'multiple' => false':
i've added this function in the other entity in relation with Manifestation, to consider manifestations as an array
public function setManifestations($manifestations){
if(!is_array($manifestations)){
$manifestations = array($manifestations);
}
$this->manifestations = $manifestations;
}