Same field multiple times Symfony 4 form - forms

I have an Entity Report, which is holds many Answers.
class Report
{
/**
* #ORM\OneToMany(targetEntity="App\Entity\Answer", mappedBy="report")
*/
private $answers;
...
}
class Answer
{
/**
* #ORM\ManyToOne(targetEntity="Report", inversedBy="answers")
*/
private $report;
...
}
I add the questions manually in my ReportType (I know this is not the best aproach but the questions will never change and I'm only interested in the answers.)
class ReportAnswersType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('answers', ChoiceType::class, array(
'label' => 'Room state',
'multiple' => false, 'expanded' => false,
'choices' => array('OK' => 'OK', 'NG' => 'NG', 'NP' => 'NP',),
))
->add('answers', IntegerType::class, array(
'label' => 'Temperature',
))
->add('answers', ChoiceType::class, array(
'label' => 'Is it good?',
'multiple' => false, 'expanded' => false,
'choices' => array('YES' => 'YES', 'NO' => 'NO',),
))
...
}
->add('save', SubmitType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Report::class,
));
}
What I would like this to do is to generate a Form with 3 questions and insert each of the answers in the database.
But the rendered form only shows the last question (because with each ->add('answers', ...) I replace the last one added).
I tried different solutions to solve this:
Following the documentation on How to Embed a Collection of Forms, which, consists in adding each answer (with a label of the question) to the Report in the Controller and then rendering the ReportType. The problem about this is that each of my questions have different type (Choice, Text, Integer...) and I don't know how to customize them individually.
And I also tried to do this in my ReportType but it only shows the last question added.
I appreciate any help, thanks!

I managed to get it working. In the end I followed the instructions on How to Embed a Collection of Forms.
To make it work I added the fields question and questionType to my Answer entity. And on the AnswerType.php I check everytime what type of question it is, and then I create the type of the answer based on that. It looks like this:
$builder->addEventListener(FormEvents::POST_SET_DATA, function ($event) {
$builder = $event->getForm();
$answer = $event->getData();
$questionType = $answer->getQuestionType();
$question = $answer->getQuestion();
if ($questionType == 1){
$builder->add('answer', ChoiceType::class, array(
'label' => $question,
'multiple' => false, 'expanded' => false,
'choices' => array('OK' => 'OK', 'NG' => 'NG', 'NP' => 'NP',),
));
}
else if($questionType == 2){
$builder->add('answer', ChoiceType::class, array(
'label' => $question,
'multiple' => false, 'expanded' => false,
'choices' => array('SI' => 'SI', 'NO' => 'NO',),
));
}
else ...
}

You can use the Entity–Attribute–Value model to solve this problem. I would suggest using sidus/eav-model-bundle. This would be a great deal of work but you would learn alot. Using EAV model, you can have N number of questions and answers.
I can not think of solving this using simple form types.

Related

SYMFONY FormTypeCollection with various fields

In my project I would like to be able to add a Collection on a Form. I thought about the FormTypeCollection. But the thing is, I need something like that:
A "New" button, at the end of the form and everytime you click on the new, a "mini-form" is added and you have the three input to fill: "name,text,link". I would like it to be stored in the database as artists = [name,text,link] for example. I have no idea how to do that. i don't want to add an Entity Artist because I just need this for display and I don't need it to be stored as an Entity on the database.
My code right now is like that:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('Contenu')
->add('published', CheckboxType::class, ['required' => false, 'label' => 'Publier'])
->add('title', TextType::class, ['required' => true, 'label' => 'Titre'])
->add('marketingEtiquette', TextType::class, ['required' => false, 'label' => 'Etiquette Marketing'])
->add('textLink', TextType::class, ['required' => true, 'label' => 'Texte du lien'])
->add('shoppingLink', TextType::class, ['required' => true, 'label' => 'Lien'])
->add('media', ElFinderType::class, array(
'label' => 'Photo',
'instance' => 'form',
'enable' => true,
'required' => true,
'attr' => array('class' => 'form-control')
)
)
->add('position',ChoiceType::class, array(
'label' => 'Position dans la page',
'choices' => array(
'Bloc Artistes' => 'artists',
'Bloc haut de page' => 'top',
'Bloc bas de page' => 'bottom'
)
))
->add('artists',CollectionType::class,array(
'label' => 'Les artistes',
'allow_add' => true,
))
->end();
}
I don't know how to add 3 fields to the field artists and generate them on the add button click. i don't even know if it's possible actually. I also don't know what should be the type 'artists' in the database.
EDIT:
I would have like to do something similar to that, so I don't need to create an Entity nor a FormType:
->add('artists',CollectionType::class,array(
'entry_type' => TextType::class ,
'entry_options' => [
'artistName' => TextType::class,
'artistText' => TextType::class,
'artistLink' => TextType::class,
],
'label' => 'Les artistes',
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'by_reference' => false
))
But it's not working so I guess I can't. Error:
The current field `artists` is not linked to an admin. Please create one for the target entity : ``
EDIT 2:
I created my ArtistFormType:
class ArtistFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('artistName', TextType::class, array(
'label' => 'Nom de l\'artiste'
))
->add('artistText', TextType::class, array(
'label' => 'Texte sous l\'artiste'
))
->add('artistLink', TextType::class, array(
'label' => 'Lien vers l\'artiste'
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => null,
]);
}
}
I called it like that:
->add('artists',CollectionType::class,array(
'entry_type' => ArtistFormType::class,
'label' => 'Les artistes',
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'by_reference' => false
))
But I get the same error:
The current field `artists` is not linked to an admin. Please create one for the target entity : ``
EDIT : This solution is limited to Symfony\Component\Form\Extension\Core\Type\CollectionType, it might be different with the one from Sonata Admin.
You can find some information on this in the documentation.
The "easiest" way to do that, is to create another form (ArtistFormType for example) with the fields you wish to add.
Then in your parent form you add the CollectionType :
$formMapper
// ...
->add('artists', CollectionType::class, [
'entry_type' => ArtistFormType::class,
'label' => 'Artists',
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'by_reference' => false
])
;
Then if you already have some artists in your entity it will render the ArtistFormType once for each artist.
If you want to add new artists it is a bit trickier and requires some Javascript.
First, render the collection inside an <ul></ul> tag :
<ul class="artists">
{{ form_row(form.artists) }}
</ul>
Following this jsfiddle :
Find the collection and add an "add artist" button to it
<script>
var $addArtistLink = $('Add an artist');
var $newLinkLi = $('<li></li>').append($addArtistLink);
jQuery(document).ready(function() {
// Get the ul that holds the collection of artists
var $collectionHolder = $('ul.artists');
// add the "add a tag" anchor and li to the tags ul
$collectionHolder.append($newLinkLi);
// ...
}
</script>
Add the "click" event with some dom manipulations to add new artists :
jQuery(document).ready(function() {
// ...
// count the current form inputs we have (e.g. 2), use that as the new
// index when inserting a new item (e.g. 2)
$collectionHolder.data('index', $collectionHolder.find(':input').length);
$addArtistLink.on('click', function(e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
// add a new artist form
addArtistForm($collectionHolder, $newLinkLi);
});
});
function addArtistForm($collectionHolder, $newLinkLi) {
// Get the data-prototype explained earlier
var prototype = $collectionHolder.data('prototype');
// get the new index
var index = $collectionHolder.data('index');
// Replace '$$name$$' in the prototype's HTML to
// instead be a number based on how many items we have
var newForm = prototype.replace(/__name__/g, index);
// increase the index with one for the next item
$collectionHolder.data('index', index + 1);
// Display the form in the page in an li, before the "Add an artist" link li
var $newFormLi = $('<li></li>').append(newForm);
// also add a remove button
$newFormLi.append('x');
$newLinkLi.before($newFormLi);
// handle the removal
$('.remove-artist').click(function(e) {
e.preventDefault();
$(this).parent().remove();
return false;
});
}
Then it should work fine. Of course some changes might be done to fit your project (field names, add the remove button on existing artists, ...).

Not to validate the form in Symfony 2

Here is my form, I build in the controller:
$defaulData = ....
$formBuilder = $this->createFormBuilder($defaultData);
$entity_options = array(
'class' => 'MyBundle:Param',
'property' => 'description',
'query_builder' => function(EntityRepository $er) {
return $er->getParamsFromCategorieQueryBuilder(MyController::$category_for_list);
},
'label' => 'Donnée à afficher'
);
$formBuilder
->add('entity_types', 'entity', $entity_options);
The form is a list of Param objects, it displays good but for some reason, when I submit the form, I have an error on entity_types field saying that the value cannot be blank though there is one Param selected (even by default).
So I was wondering if I could disable validation.
When whould I put this validation_groups to false ? if it is in $entity_options, I tried it already and it does not work.
ty
You should modify you options like this (both require and groups are needed):
$entity_options = array(
'class' => 'MyBundle:Param',
'property' => 'description',
'query_builder' => function(EntityRepository $er) {
return $er->getParamsFromCategorieQueryBuilder(MyController::$category_for_list);
},
'required' => false,
'validation_groups' => false,
'label' => 'Donnée à afficher'
);
If the error is instead on the Form class itself, you should change in:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => false,
));
}
The way to prevent validation for good is this one :
$formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
$event->stopPropagation();
}, 900);
It prevents from calling the validation event.
The solution that giosh94mhz gave me what not good because even with validation_group = false some validation are still done.

Symfony form creates new object and create first one-to-many object

I have an entity for support tickets: SupportTicket(). I also have an entry for replies to each ticket: SupportEntry(). I setup a one-to-many relationship between SupportTicket() and SupportEntry().
Now what I'm trying to do is build my form so that it creates the initial SupportTicket and then inserts the first SupportEntry, all in the same form. I've been messing around with my code for a while, only half-understanding what I'm doing, but this is where I'm at right now:
// My controller, creating the form
$supportTicket = new SupportTicket();
$form = $this->createFormBuilder($supportTicket)
->add('subject', 'text', array(
'label' => 'Subject'
))
->add('jobNumber', 'text', array(
'label' => 'Job Number'
))
->add('supportGroup', 'entity', array(
'label' => 'Group',
'class' => 'ShawmutClientBundle:SupportGroup',
'property' => 'name',
'multiple' => true,
'expanded' => true
))
// ->add('supportEntries', new SupportEntryType())
->add('supportEntries', new SupportEntryType())
->add('Save', 'submit')
->getForm();
My attempt at the custom form type
<?php
namespace Shawmut\ClientBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SupportEntryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('comment', 'textarea');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Shawmut\ClientBundle\Entity\SupportEntry',
));
}
public function getName()
{
return 'SupportEntryType';
}
}
The form does have the comment box that I've pulled in from the form type, but when I try to submit the form, I get this error:
Neither the property "supportEntries" nor one of the methods "setSupportEntries()", "_set()" or "_call()" exist and have public access in class "Me\MyBundle\Entity\SupportTicket".
And yeah, that makes sense. It should be the addSupportEntries() method which is there. So how do I tell the form builder to use addSupportEntries instead of setSupportEntries?
Thanks in advance
Give the collection form type a go.
->add(
'supportEntries',
'collection',
array(
'type' => new SupportEntryType(),
'label' => 'Support Entries',
'error_bubbling' => true,
'cascade_validation' => true,
)
)
If you are using the collection form type, and the textarea is not showing, add:
'allow_add' => true
to the properties array().
The code would look something like this:
->add(
'supportEntries',
'collection',
array(
'type' => new SupportEntryType(),
'label' => 'Support Entries',
'error_bubbling' => true,
'allow_add' => true
'cascade_validation' => true,
)
)
To show the widget, assuming you are using twig:
{{ form_widget(form.supportEntries.vars.prototype.comment) }}
For saving the support entry, depending on how you built your entities, you might need to make some extra modifications.
The documentation should help you get it right:
How to Embed a Collection of Forms

Symfony2 form populating entitiy based on other entity

I'm using Symfony2 form component to create and modify forms. I'm currently loading all model entities as choices, but I need to get only the Models related to the selected (posted) value from manufacturer.
How can I achieve this?
class VehicleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('manufacturer', 'entity', array(
'label' => 'Brand',
'attr' => array('class' => 'input-block-level'),
'class' => 'BalslevCarBundle:Manufacturers',
'property' => 'short_name',
'empty_value' => 'All',
'required' => false
));
$builder->add('model', 'entity', array(
'label' => 'Model',
'class' => 'BalslevCarBundle:Models',
'property' => 'model',
'empty_value' => 'All',
'required' => false
));
}
public function getName()
{
return 'search';
}
}
First of all, you have to create "dynamically" your choices based onto user selection.
There are two ways, strictly coupled with your application behaviour, that leads (in both cases) at the same "core" code; the difference is only the way you call it.
So, let's explain the two possibilities:
Select manufacturer "statically" and don't change it over the time of request: use data retrieved from your business logic
Select manufacturer "dinamically" and can change them over the time of request: replace your form at every request; new form will be provided by business logic
So, the HEART of your question is: how can I retrieve only associated entities?
Answer
Into your VehicleType.php (where entity, in this case, is VehicleType ?)
private $manufacturerId;
public function __construct($manufacturerId)
{
$this->manufacturerId = $manufacturerId;
}
public function BuildForm(FormBuilderInterface $builder)
{
$manufacturerId = $this->manufacturerId;
$manufacturerQuery = function(EntityRepository $repo) use ($manufacturerId ){
return $repo->createQueryBuilder('m')
->where('m.id = :id')
->setParameter('id',$manufacturerId );};
$builder->add('manufacturer', 'entity', array(
'label' => 'Brand',
'attr' => array('class' => 'input-block-level'),
'class' => 'BalslevCarBundle:Manufacturers',
'property' => 'short_name',
'empty_value' => 'All',
'required' => false,
'query_builder' => $manufacturerQuery;
));
$builder->add('model', 'entity', array(
'label' => 'Brand',
'attr' => array('class' => 'input-block-level'),
'class' => 'BalslevCarBundle:Manufacturers',
'property' => 'short_name',
'empty_value' => 'All',
'required' => false,
'query_builder' => $modelQuery;
));
}
As you can see, in this way we use a custom repository to get only entities that are suitable for our needs. Obviously I am writing my DQL query without knowing your DB structure: it's your task and responsibility to adapt it to your real situation.
Moreover, you have to create another query for model (called $modelQuery in my answer) to retrieve correct models based on selected $id

How to implement the event listener to a radio button on Symfony2?

This is my form
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('nombreBD','text', array( 'required' => true, 'label' => 'Nombre Base de Datos: '))
->add('Servidor', 'choice', array(
'choices' => array('1' => 'Si', '0' => 'No'),
'required' => true,
'multiple' => false,
'expanded' => true,
'label' => 'Servidor Existente?'
))
->add('ServidorBD','entity',
array ('class' => 'MonseWebBundle:ServidoresBD',
'multiple' => true,
'required' => true,
'label' => 'Servidor de Base de Datos: ',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.url', 'ASC');
},
))
;
}
and what i'm trying to do is if the User chooses "No" in the radio button, the "ServidorBD" entity shouldn't display and it should display instead another form (loading it dynamically or redirecting the user to another url) to add a new one.
Since i'm new to Symfony2, i don't quite understand how to attach the eventlistener to the "radio button" nor how to display another bit of form instead of the "ServidorBD" when this happens.
PLEASE HELP! T-T
What you want to do is build your form dynamically according to datas you will bind on your form.
In Symfony 2.0.x or 2.1.x, the form component is not able to to alter the form structure after binding datas on it. That will be done in Symfony 2.2.
See this issue: https://github.com/symfony/symfony/issues/3767
So, currently, you can't use the form event listener to archive this use case.