Symfony 3.4 Collection Embedding With Default Value - symfony

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,
]
)

Related

Add dynamically option to all fields of some type of symfony form

I have many fields of type subclass of EntityType, something like this:
$builder->add('managers', SubclassEntityType::class, [
'class' => 'User',
'choice_label' => 'Managers',
'required' => false,
'query_builder' => function (UserRepository $er) {
return $er->getManagersQueryBuilder();
},
'multiple' => true,
]);
$builder->add('types', SubclassEntityType::class, [
'class' => 'Type',
'choice_label' => 'Types',
'required' => false,
'query_builder' => function (TypesRepository $er) {
return $er->getManagersQueryBuilder();
},
'multiple' => true,
]);
Could I dynamically add option (option of fields in select, not option of form) to all fields of the same type like this
(Empty) => 'empty? I don't want to customize it for each field. I need this option to filter allow to add to field null values, for examples, find entities to which not manager is assigned.
Would it be easier to solve this if it were subclass of ChoiceType?
I tried to subclass ChoiceType и add empty option in buildView, but in this case validation fails as well. Does anybody know how to add option and make validation work? It looks like adding option in buildView doesn't solve the problem.
if isn't multiple ,You can achieve that by defining tow options placeholder and empty_data
class YourType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('managers', SubclassEntityType::class, [
'placeholder' => 'Please choose a manager..',
'empty_data' => null,
'class' => 'User',
'choice_label' => 'Managers',
'required' => false,
'query_builder' => function (UserRepository $er) {
return $er->getManagersQueryBuilder();
},
]);
$builder->add('types', SubclassEntityType::class, [
'placeholder' => 'Please choose a type..',
'empty_data' => null,
'class' => 'User',
'class' => 'Type',
'choice_label' => 'Types',
'required' => false,
'query_builder' => function (UserRepository $er) {
return $er->getManagersQueryBuilder();
},
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
// ...
]);
}
}
For multiple choices we can use finishView method for creating new choices and add them on children of your type.
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class YourType extends AbstractType
{
// If you have in the future other type Entity Type and you want to add option empty
// You can just add it on this list
private const OPTION_EMPTY_ENTITIES_TYPE = [
// child name => 'Message on option empty',
'managers' => 'Select a manager..',
'types' => 'Select a type..',
];
private const OPTION_EMPTY_KEY ='option-empty';
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('managers', SubclassEntityType::class, [
'class' => 'User',
'choice_label' => 'Managers',
'required' => false,
'query_builder' => function (UserRepository $er) {
return $er->getManagersQueryBuilder();
},
'multiple' => true,
]);
$builder->add('types', SubclassEntityType::class, [
'class' => 'Type',
'choice_label' => 'Types',
'required' => false,
'query_builder' => function (UserRepository $er) {
return $er->getManagersQueryBuilder();
},
'multiple' => true,
]);
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
// remove all options we has add on finishView
foreach (self::OPTION_EMPTY_ENTITIES_TYPE as $childName => $optionMessage) {
if (false !== ($key = array_search(self::OPTION_EMPTY_KEY ,$data[$childName]))) {
unset($data[$childName][$key]); // example $data['managers'][0] => option-empty
}
}
$event->setData($data);
});
}
public function finishView(FormView $view, FormInterface $form, array $options)
{
foreach (self::OPTION_EMPTY_ENTITIES_TYPE as $childName => $optionMessage) {
// value option-empty is not a valid option of EntityType X ,otherwise if user select this option
// this form is invalid...
$newChoice = new ChoiceView(null, self::OPTION_EMPTY_KEY, $optionMessage);
array_unshift($view->children[$childName]->vars['choices'], $newChoice);
}
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
// ..
]);
}
}

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 modify choices in collection after set data

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()
]);
});
}

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