I've embedded forms with CollectionType:
$builder->add('battlePages', CollectionType::class, [
'label' => 'Battle Pages',
'entry_type' => BattlePageCollectionType::class,
'error_bubbling' => false,
'constraints' => [
new Valid(),
],
]);
and 'battlePages' is an ArrayCollection with many elements.
public function buildForm(FormBuilderInterface $builder, array $options) {
/** #var BattlePage $entity */
$entity = $builder->getData();
...
But '$entity' is empty however collection was walking through.
My goal would be to get BattlePage entity's data in the 'BattlePageCollectionType' which is my second in examples ($entity).
Anybody had have similar issues?
The $builder->getData(); method doesn't return an entity.
You should use Form Events
Example with PRE_SET_DATA event:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$entity = $event->getData();
$form = $event->getForm();
$form->add('someField', TextType::class);
});
Related
Following problem I have: In Symfony (Version 4.4.22) I created a FormType with a date-field and a checkbox. If the checkbox was checked then the field should get the value of "31.12.9999".
If a requesting form has the value 1 for the field infiniteValidTo, the value of validTo should change from empty to "31.12.9999". (In my case the date field has the value 'null' when the form was submitted.)
So I added an EventListener to the form builder with a pre_submit hook that will add this info before the form is validating.
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('validTo', DateType::class, [
'required' => FALSE,
'format' => 'dd.MM.yyyy'
])
->add('infiniteValidTo', CheckboxType::class, [
'required' => FALSE
])
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
if (isset($data['infiniteValidTo']) && $data['infiniteValidTo'] === '1') {
$data['validTo'] = '31.12.9999';
}
$event->setData($data);
});
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'data_class' => SettingFormModel::class,
'constraints' => [
new Callback([
'callback' => [$this, 'validateFormModel']
])
]
]);
}
/**
* #param SettingFormModel $object
* #param ExecutionContextInterface $context
*/
public function validateFormModel(SettingFormModel $object, ExecutionContextInterface $context): void {
dump($object);
}
Before leaving the listener method the data-array has the correct values (by dumping the variable).
For validating the form in a dynamical way, I defined a callback method for the data object. When the data container arrives the methods, my change of the validTo field is gone. If I change the field into a simple text field it works, but not for a date field.
After debugging a lot of time, I saw that the method mapFormsToData doesn't transform the change into the form object.
Do I made a mistake by configuration or is this a bug in symfony? Has somebody else the same issue with a form?
I found the mistake. The setter of SettingFormModel was not correct. After repairing the Listener works as it should.
You can use a post_submit event
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('validTo', DateType::class, [
'required' => FALSE
])
->add('infiniteValidTo', CheckboxType::class, [
'required' => FALSE
]);
$builder->get('infiniteValidTo')->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
if ($event->getForm()->getData()) {
$event->getForm->getParent()->getData()->setValidTo(new \DateTime('9999-12-31'));
}
});
}
i have a InspectionPlan Form Type which has a product EntityType field, (Products can have attributes i assign some by fixtures) however, the formtype also has a CollectionType Field for another entity called InspectionPlanSections, this uses a InspectionPlanSectionFormType which has a CollectionTypeField for an entity called InspectionPlanQuestions with a InspectionPlanQuestionFormType with allow_add' => true and a prototype, these questions have a relation to the earlier mentioned ProductAttributes, , only the product attributes the earlier submittet Product has should be assignable
class InspectionPlanQuestionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('question', TextareaType::class, [
'attr' => ['class' => 'tinymce'] ])
->add('productAttribute', EntityType::class, [
'class' => ProductAttribute::class,
'placeholder' => 'Attribut auswählen',
'choice_label' => 'name'
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => InspectionPlanQuestion::class,
]);
}
}
this loads all product attributes which is not exactly what i want
class InspectionPlanQuestionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('question', TextareaType::class, [
'attr' => ['class' => 'tinymce'] ]);
$builder->addEventListener(FormEvents::PRE_SET_DATA,
function (FormEvent $event)
{
$form = $event->getForm();
$child = $event->getData();
if ($child) {
$form->add('productAttribute', ChoiceType::class, [
'placeholder' => 'forms.product.attribute_placeholder',
'choices' => $child->getInspectionPlanSection()->getInspectionPlan()->getProduct()->getProductAttributes(),
'choice_label' => function ($choice, $key, $value) {
return $choice->getName();
},
]);
}
}
);
}
this gives me the desired attributes in the select, but only after i atleast submitted one section with a question, because if none were submitted, the $event->getData() returns always null
so when i create a fresh InspectionPlan and add a product, submit the form, and then add a Section and questions via javascript, i get no product attributes select field rendered in my question field
Can someone explain me why this works, after i submitted at least one question?
I have the following form that contains data from the database it still WIP ( i'm missing a few fields that i didn't add yet).
The form loads data in the first select and based on that select i use ajax to populate a second select with options based on the first select ( basically the associations to the selected value). And from there again another select with certain options and so on and at the end when i submit the form i want to generate a report from database based on the data.
For the moment i'm stuck with the second field because i always get an error:
This value is not valid.
The form class:
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('survey', EntityType::class, [
'class' => SurveyManager::class,
'placeholder' => 'Choose option',
'attr' => [
'class' => 'field-change',
],
])
->add('headquarter', ChoiceType::class, [
'choices' => [],
])
->add('submit', SubmitType::class, [
'label' => 'Save',
])
;
}
I'm not reall sure how to fix the error or how should i handle this type of form. Can you help me out guys ?
Based on the answer i did this
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
$form->add('headquarter', EntityType::class, [
'class' => HeadQuarterManager::class,
'query_builder' => function(HeadQuarterManagerRepository $er) {
return $er->getHeadquarter($data['survey']);
},
]);
}
);
But i'm getting this error:
Notice: Undefined variable: data
Not really sure how to pass the data to the getHeadquarter method so i can return an array of id => name for the select.
When you run the function $form->isValid(), it checks against the form it built in the buildForm function. Any extra fields/value that aren't there will cause this error.
You can change this behaviour by using form events.
In the end this is how i did it:
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('survey', EntityType::class, [
'class' => SurveyManager::class,
'attr' => [
'class' => 'field-change',
],
])
->add('submit', SubmitType::class, [
])
->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
$modifier = $data['survey'];
$form->add('headquarter', EntityType::class, [
'class' => HeadQuarterManager::class,
'query_builder' => function (HeadQuarterManagerRepository $er) use ($modifier) {
return $er->getHeadquarter($modifier);
},
]);
}
);
}
I'm trying to create a form field with a set of choices with an extra text input which needs to be filled out if you choose 'other':
How often do you exercise?
(*) I do not exercise at the moment
( ) Once a month
( ) Once a week
( ) Once a day
( ) Other, please specify: [ ]
Currently, I'm using a ChoiceType where I have set my choices like this:
$form->add('exercise', Type\ChoiceType::class, array(
'label' => 'How often do you exercise?',
'choices' => [ 'I do not excerise at the moment' => 'not', ... ],
'expanded' => true,
'multiple' => false,
'required' => true,
'constraints' => [ new Assert\NotBlank() ],
));
How do I get the 'other, please specify' option to work as expected?
In this case you will need to create custom form type which will be combination of ChoiceType and TextType. Nice intro to custom form types can be find id doc: http://symfony.com/doc/master/form/create_custom_field_type.html
This should be something similar to:
class ChoiceWithOtherType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// prepare passed $options
$builder
->add('choice', Type\ChoiceType::class, $options)
->add('other', Type\TextType::class, $options)
;
// this will requires also custom ModelTransformer
$builder->addModelTransformer($transformer)
// constraints can be added in listener
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
// ... adding the constraint if needed
});
}
/**
* {#inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
// if needed
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
//
}
));
Please take a look at:
chapter about data transformers: http://symfony.com/doc/current/form/data_transformers.html
dynamic form modifications: http://symfony.com/doc/current/form/dynamic_form_modification.html
I think the best way to achieve it is to take a look at the source code of the DateTimeType.
I am trying to build a RESTful API with Symfony2, FosRestBundle, and JMSSerializer and everything works perfect until I try to persist an entity with any kind of association. This works out-of-the-box with the standard symfony edition, it's enough to set the form field as "entity" type and the framework updated the cross table. However when I try to do the same on a REST setup, I get this error "Notice: Array to string conversion" related to the "categories" form field and a 500 error.
How I am supposed to persist related entities with an API?
Entity Serie.php
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="series")
* #ORM\JoinTable(name="categories_series")
*/
private $categories;
a form SerieType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('categories', 'entity', array(
'multiple' => true,
'expanded' => false,
'property' => 'name',
'class' => 'My\Bundle\CoreBundle\Entity\Category'
))
->add('name')
->add('description')
->add('status', 'checkbox', array('required' => false))
->add('save', 'submit')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'My\Bundle\CoreBundle\Entity\Serie',
'csrf_protection' => false
));
}
public function getName()
{
return 'serie';
}
and a SerieController.php
/**
* #POST("", name="serie_new")
*/
public function newAction(Request $request){
$serie = new Serie();
$form = $this->createForm(new SerieType(), $serie);
$form->submit($request->request->get($form->getName()));
if($form->isValid()){
$em = $this->getDoctrine()->getManager();
$em->persist($serie);
$em->flush();
$view = $this->routeRedirectView('serie_index');
return $this->handleView($view);
}
and a json POST request:
{
"serie": {
"categories":[
{"id":1,"name":"Category 1"},
{"id":2,"name":"Category 2"}
],
"name":"asasdasd",
"description":"sadsads",
"status":true
}
You error is probably generated when trying to display the entity. Try adding __toString() method to your Category Entity.
And you should use a DataTransformer in order to be able to identify the category when trying to submit a POST request.