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

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

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

Expected argument of type "string", "null" given at property path

Actually when I try to edit the form by sending empty fields, the above error comes on ,
My UserType class looks like:
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName', null, [
'label' => 'Prénom'
])
->add('lastName', null, [
'label' => 'Nom'
])
->add('email', EmailType::class, [
'label' => 'Adresse e-mail'
])
->add('password', PasswordType::class, [
'label' => 'Mot de passe'
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
This problem can be resolved by adding 'empty_data' param in the builder add function:
So the new UserType classe becomes:
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName', null, [
'label' => 'Prénom',
**'empty_data' => ''**
])
->add('lastName', null, [
'label' => 'Nom',
**'empty_data' => ''**
])
->add('email', EmailType::class, [
'label' => 'Adresse e-mail',
**'empty_data' => ''**
])
->add('password', PasswordType::class, [
'label' => 'Mot de passe',
**'empty_data' => ''**
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
Instead of #Mustapha GHLISSI answer, you can set your field to accept null values:
<?php
class User
{
public ?string $firstName;
// your other fields
}
The public ?string $firstName will accept string and null values for the field firstName.
Before PHP 8.0 its maybe:
public function setFirstName(string $firstName = null): self
{
$this->firstName = $firstName;
return $this;
}

How to use "selected" in option with the user's locale in the buildForm function?

I would like to make a form with "selected" in option to choose a language. By default, I would like to "selected" User's locale option.
(...)
use Symfony\Component\HttpFoundation\Request;
(...)
class VisitType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options, Request $request)
{
$locale = $request->getLocale();
if ($locale == 'fr'){
$prefLang = "Français";
}elseif ($locale == 'nl'){
$prefLang = "Nederlands";
}elseif ($locale == 'en') {
$prefLang = "English";
}
$builder->add('langvis', ChoiceType::class, [
'label' => 'form.input.langvis',
'choices' => [
'Français' => 'Français',
'Nederlands' => 'Nederlands',
'English' => 'English'
],
'preferred_choices' => [$prefLang]
])
->add('save', SubmitType::class, [
'label' => 'form.input.send',
'attr' => [
'class' => 'btn btn-primary form-control'
]
])
->getForm();
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Visit::class
]);
}
}
But I have a compile Error:
Declaration of
App\Form\VisitType::buildForm(Symfony\Component\Form\FormBuilderInterface
$builder, array $options, Symfony\Component\HttpFoundation\Request
$request) must be compatible with
Symfony\Component\Form\AbstractType::buildForm(Symfony\Component\Form\FormBuilderInterface
$builder, array $options)
How can I do that?
you cannot pass request on buildForm, try using OptionsResolver
class VisitType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$locale = $options["locale"];
if ($locale == 'fr') {
$prefLang = "Français";
} elseif ($locale == 'nl') {
$prefLang = "Nederlands";
} elseif ($locale == 'en') {
$prefLang = "English";
}
$builder->add('langvis', ChoiceType::class, [
'label' => 'form.input.langvis',
'choices' => [
'Français' => 'Français',
'Nederlands' => 'Nederlands',
'English' => 'English'
],
'preferred_choices' => [$prefLang]
])
->add('save', SubmitType::class, [
'label' => 'form.input.send',
'attr' => [
'class' => 'btn btn-primary form-control'
]
])
->getForm();
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'data_class' => Visit::class,
'locale' => 'Français'
]);
}
In controller, when you create form pass locale parameter:
$locale = $request->getLocale();
$formType = 'VisitType';
$entity = 'VisitEntity';
$form = $this->createForm($formType, $entity, array('locale' => $locale));

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

Resources