symfony modify choices in collection after set data - symfony

I'm trying to build a form, for user to enter his answers to an exam.
Form consists of Collection of answers looking like this:
class UserExamFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('answers', CollectionType::class,
[
'entry_type' => UserExamAnswerType::class,
'allow_delete' => false,
'allow_add' => false,
])->add('submit', SubmitType::class,
[
'label' => 'Zapisz',
'attr' => [
'class' => 'btn-success',
],
]
)->add('finish', SubmitType::class,
[
'label' => 'Zapisz i zakończ',
'attr' => [
'class' => 'btn-danger confirmation',
'data-message' => 'Czy na pewno chcesz zakończyć test? Nie będzie możliwości poprawy odpowiedzi.'
],
]
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => UserExam::class,
]);
}
}
class UserExamAnswerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// dump($builder->getData()); === null
$builder
->add('answer', EntityType::class,
[
'class' => Answer::class,
'choice_value' => 'id',
'expanded' => true,
'query_builder' => function (EntityRepository $entityRepository) {
return $entityRepository->createQueryBuilder('a');
}
]);
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) {
dump($event->getData()); // !== null
//modify choices
});
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ExamAnswer::class,
]);
}
}
How to modify choices in the commented line?
I'd like the choices to be affected by the object passed into the form.
In the line dump($event->getData()); // !== null I get exactly the object I need to determine choices but I haven't found any info on it - only dynamically adding a field.

I finally made it by adding an element right before Symfony sets the data on form.
Maybe someone will profit from this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$event->getForm()
->add('answer', EntityType::class,
[
'class' => Answer::class,
'choice_value' => 'id',
'expanded' => true,
'choices' => $event->getData()->getQuestion()->getAnswers()
]);
});
}

Related

Validations Groups doesn't working with forms

I'm trying to setup groups validation on my symfony project.
When I update an entity, I only need to validate some fields. When I create an entity, I only need to validate some other fields.
Service:
$form = $this->formFactory->createNamed('form', FormType::class, $entity, ['validation_groups' => ['update']]);
Form:
class FormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('user', EntityType::class, [
'class' => User::class,
'validation_groups' => ['create']
])
->add('number', EntityType::class, [
'class' => Numbers::class,
'validation_groups' => ['create', 'update']
])
->add('phone', TextType::class, [
'validation_groups' => ['create', 'update']
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Phones::class,
'allow_extra_fields' => true,
'validation_groups' => ['create', 'update'],
'cascade_validation' => true,
]);
}
}
But, when I submit my form, the "user" field still validated.
{"form":{"user":748,"number":"9.2","phone":"0x xx xx xx xx"}}
{"id":957,"error":"Expected argument of type \"App\\Entity\\User\", \"null\" given at property path \"user\"."}
You don't need to specifify validation_groups on fields.
Groups passed into form options are applied to all fields by default.
# Service 1
// Only 'create' group will be validated
$this->formFactory->createNamed('form', FormType::class, $entity, ['validation_groups' => ['create']]);
# Service 2
// Only 'create' and 'update' group will be validated
$this->formFactory->createNamed('form', FormType::class, $entity, ['validation_groups' => ['create', 'update']]);
# Form
class FormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('user', EntityType::class, [
'class' => User::class,
// 'validation_groups' => ['create']
])
->add('number', EntityType::class, [
'class' => Numbers::class,
// 'validation_groups' => ['create', 'update']
])
->add('phone', TextType::class, [
// 'validation_groups' => ['create', 'update']
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Phones::class,
'allow_extra_fields' => true,
// 'validation_groups' => ['create', 'update'],
'cascade_validation' => true,
]);
}
}
However, this doesn't seem like form validation error (violation).
Expected argument of type \"App\\Entity\\User\", \"null\" given at property path \"user\" means, that value passed into 'user' field is null (not set).
You should omit 'user' field when it form is used for 'creation'.
Solution 1:
// add option
$resolver->setDefaults([
'data_class' => Phones::class,
// ...
'addUserField' => null,
]);
// in service pass this option
$this->formFactory->createNamed('form', FormType::class, $entity, [
'validation_groups' => ['create'],
'addUserField' => true
]);
// use condition in form to add field only if option is passed with true value
if($options['addUserField'] === true) {
$builder->add('user', EntityType::class, [class' => User::class]);
}
$builder
->add('number'/** ... */)
->add('phone'/** ... */)
;
Solution 2:
Create 2 form types FormTypeCreate and FormTypeUpdate. Skip 'user' field in FormTypeUpdate. You could also extend FormTypeCreate from FormTypeUpdate.
class FormTypeUpdate extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('number', EntityType::class, [
'class' => Numbers::class,
])
->add('phone', TextType::class)
;
}
//...
}
//
class FormTypeCreate extends FormTypeUpdate
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('user', EntityType::class, [
'class' => User::class,
])
parent::buildForm($builder, $options); // adds 'number' and 'phone' fields
}
//...
}

Symfony: Collection and subform required option & validator not working

I have a Form which embeds a collectionType.
The Collection type uses two subforms.
When I set required option to fields belonging to CollectionType form or Subforms, it does not work: neither red * nor form validation errors...
I have a Form which embeds a collectionType.
The Collection type uses two subforms.
When I set required option to fields belonging to CollectionType form or Subforms, it does not work: neither red * nor form validation errors...
What did I miss ?
Classe:
class: App\Entity\Classe
label: 'Classes'
form:
fields:
- { property: 'classe', label: 'Classe Name'}
- { property: 'maxextcuth', label: 'Max ext. Cu', type: 'entity'}
- { property: 'maxintcuth', label: 'Max int. Cu', type: 'entity'}
- { property: 'enabled', label: 'Enabled'}
- { type: 'section', label: 'Price rules', icon: 'euro' }
- property: 'classePrices'
label: false
type: 'collection'
type_options:
entry_type: App\Form\Type\ClassePriceType
allow_delete: true
allow_add: true
by_reference: false
prototype: true
block_prefix: 'price_collection'
class ClassePriceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('priceform', PriceformType::class, [
'data_class' => ClassePrice::class,
'block_prefix' => 'mainprice_block'
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ClassePrice::class,
'attr' => array(
'class' => 'fullwidth'
)
]);
}
}
class PriceformType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('pricecover', ChoiceType::class, [
'label' => 'Price type',
'placeholder' => 'Select a price option',
'choices' => [ 'Global' => '1' ,'Step' => '2'],
'required' => true
])
->add('rangesubform', RangesubformType::class, [
'data_class' => ClassePrice::class,
'block_prefix' => 'range_block'
])
->add('pricesubform', PricesubformType::class, [
'data_class' => ClassePrice::class,
'block_prefix' => 'price_block'
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'inherit_data' => true
]);
}
}
class RangesubformType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('rangetype', EntityType::class, [
'class' => Ptur::class,
'label' => 'Step type',
'choice_translation_domain'=> true,
'required' => true
])
->add('rangeformat', EntityType::class, [
'class' => Format::class,
'label' => 'Format',
'required' => true
])
->add('rangemin', IntegerType::class, [
'label' => 'Range min',
'required' => true
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'inherit_data' => true,
'attr' => array(
'class' => 'form-horizontal'
)
]);
}
}

Symfony 3.4 Collection Embedding With Default Value

I have an issue with passing the default value to a form. It just doesn't appear in the form.
I've tried to follow the official documentation, and seems to be configured correctly.
The $facets_landing_page is a doctrine entity from ->find($id) with a one to many relationship. facetsLandingPage is the name of the collection (containing many) inside the $facets_landing_page object.
If I pass $facets_landing_page as a 'data' option directly into the ->add function, it shows in the form but then has issues on save submit.
Form creation:
$formBuilder = $this->createFormBuilder($facets_landing_page)
->add('facetsLandingPage', FacetsLandingPageType::class);
Then $form->createView() etc.
The custom type:
class FacetsLandingPageType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add(
'facetsLandingPage', CollectionType::class, [
'entry_type' => FacetsLandingPageDescriptionType::class,
'entry_options' => [
'label' => false,
],
'by_reference' => false,
'allow_add' => true,
'allow_delete' => true,
'label' => false,
]
);
}
public function getBlockPrefix() {
return 'flpwrapper';
}
}
The child type:
class FacetsLandingPageDescriptionType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('language', LanguageSelectType::class);
$builder->add('fec', FecSelectType::class, ['required' => false]);
$builder->add('title', TextType::class);
$builder->add('meta_title', TextType::class);
$builder->add('meta_description', TextType::class);
$builder->add('markdown', MarkdownType::class);
}
public function getBlockPrefix() {
return 'flp';
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(
[
'data_class' => FacetsLandingPageDescription::class,
'required' => false,
'attr' => [
'class' => 'collection_item',
],
]
);
}
public function buildView(FormView $view, FormInterface $form, array $options) {
$view->vars['tab_title'] = 'New';
if (!empty($form->getData())) {
$view->vars['tab_title'] = $form->getData()->getTabTitle();
}
parent::buildView($view, $form, $options);
}
}
You can use the prototype_data attribute in your collection declaration. You can adapt something like the following.
->add(
'collectionItems',
CollectionType::class,
[
'entry_type' => CollectionItemType::class,
'prototype_data' => new CollectionItemType()
]
)
seems like FacetsLandingPageType wasn't getting any default value from the form. So I got rid of it, and I passed the item directly into the main form
->add(
'facetsLandingPage', CollectionType::class, [
'entry_type' => FacetsLandingPageDescriptionType::class,
'entry_options' => [
'label' => false,
],
'by_reference' => false,
'allow_add' => true,
'allow_delete' => true,
]
)

Using a formtype to edit object

I am trying to use a formtype to edit one of my objects.
The formtype is working flawlessly when creating new objects, but when I try to load an existing entity into the form, I get the following exception:
Entity of type "Proxies__CG__\App\Entity\Language" passed to the choice field must be managed. Maybe you forget to persist it in the entity manager?
The object which gets put into the form is loaded from the database, so it is 100% persisted already.
Here are the types:
UnitType
class UnitType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('unitTranslations', CollectionType::class, [
'entry_type' => UnitTranslationType::class,
'entry_options' => array('label' => false),
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'by_reference' => false,
'delete_empty' => true,
'attr' => [
'class' => 'UnitTranslationSelector',
],
])
->add('save', SubmitType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Unit::class
));
}
}
UnitTranslationType
class UnitTranslationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class, [
'label' => 'Unit',
'by_reference' => false
])
->add('language', EntityType::class, [
'class' => Language::class,
'choice_label' => 'name',
'placeholder' => 'Choose an option',
'by_reference' => false
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => UnitTranslation::class,
));
}
}
and the controller:
public function createUnit(Request $request, EntityManagerInterface $entityManager)
{
$unit = new Unit();
$unit = $entityManager->getRepository(Unit::class)->find(2);
dump($unit);
$form = $this->createForm(UnitType::class, $unit);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
foreach ($unit->getUnitTranslations() as $translation) {
$translation->setUnit($unit);
}
$entityManager->persist($unit);
$entityManager->flush();
return $this->redirectToRoute("recipe_list");
}
return $this->render('createUnit.html.twig', array('form' => $form->createView()));
}
I know this question is old, but I just solved this same problem by using CollectionType instead of EntityType (for the language entity in your case). I'm not sure how your entities are set up, but the CollectionType would require a OneToMany or ManyToMany relationship between UnitTranslation entity and Language entity. Description for CollectionType can be found here and how to embed a collection of forms can be found there.

Symfony: How to add a field form to a child?

I have a field type entity that is rendered as checkboxes I want to add for each checkbox a field of type textarea, how I can I do this ?
Code:
// OfferType.php
$builder
->add('payment_method', new OfferPaymentType(), [
'required' => false,
'mapped' => false,
'expanded' => true,
'multiple' => true,
])
;
// OfferPaymentType.php
class OfferPaymentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('payment', null, [
'multiple' => true,
'expanded' => true,
'compound' => true,
])
;
$factory = $builder->getFormFactory();
$formModifier = function (FormInterface $form, $payments = null) use ($factory) {
foreach ($form as $child) {
//dump($child);die;
$child->add(
$factory->createNamed('metadata', 'textarea', null, [
'auto_initialize' => false,
'compound' => true,
]),
null,
['compound' => true]
);
}
};
$builder->get('payment')->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
$data = $event->getData();
$formModifier($event->getForm(), $data);
}
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'multiple' => true,
'expanded' => true,
'class' => 'AppBundle:OfferPayment',
'data_class' => 'AppBundle\Entity\OfferPayment',
'translation_domain' => 'app',
'compound' => true,
));
}
public function getName()
{
return 'offer_payment';
}
}
You need to create a custom form type which gonna have two embedded fields a checkbox and a textarea
class OfferPaymentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('payement', 'checkbox')
->add('metadata', 'textarea');
}
}
And in your form Type you will do something like
class CustomType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('payment_method', 'collection', array(
'type' => new OfferPaymentType(),
'allow_add' => true,
'allow_delete' => true
)
);
}
}

Resources