Symfony: Collection and subform required option & validator not working - symfony

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

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

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 Vue.js to bind value of input field to a second input field

I have Symfony Form which defined as below (minus non-pertinent fields for brevity):
<?php
namespace AppBundle\Form;
use AppBundle\Entity\Category;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CategoryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('label', TextType::class, [
'label' => 'label.display_name',
'attr' => [
'placeholder' => 'placeholder.category_name',
'class' => 'label',
'#input' => 'vUpdateSlug'
]
])
->add('slug', TextType::class, [
'label' => 'label.slug',
'attr' => [
'class' => 'slug',
'#input' => 'vUpdateSlug',
':value' => 'slug'
]
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Category::class,
'category_id' => null
]);
}
}
I am attaching Vue.js directives to both input fields. The idea is that when someone types in the label field, the slug field is automatically updated with the label input value with some minor changes (replacing spaces with hyphens). I still want the user to be able to alter the slug if they wish, but not have the label update.
The v-on:input/#input directive behaviour works, however, I'm just starting with Vue.js and my implementation feels a little clunky (repetitive) - see below:
new Vue({
delimiters: ['[[', ']]'],
el: '#category-form',
data: {
slug: this.slug = $('[name="category[slug]"]').val()
},
ready: function () {
this.slug = $('[name="category[slug]"]').val();
},
methods: {
vUpdateSlug: function (event) {
var str = event.target.value.replace(/[^a-zA-Z0-9 -]/g, '').replace(/\s+/g, '-').toLowerCase();
return this.slug = str;
}
}
});
Is there a better solution to my problem?
After more researching and tinkering, I came up with the following which produces the desired result:
CategoryType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('label', TextType::class, [
'label' => 'label.display_name',
'attr' => [
'placeholder' => 'placeholder.category_name',
'class' => 'label',
'v-model' => 'label'
]
])
->add('slug', TextType::class, [
'label' => 'label.slug',
'attr' => [
'class' => 'slug',
'#input' => 'setSlug',
':value' => 'slug'
]
]);
}
Vue Script
new Vue({
delimiters: ['[[', ']]'],
el: '#form-wrapper',
data: {
label: $('[name="category[label]"]').val(),
slug: $('[name="category[slug]"]').val()
},
watch: {
label: function(newLabel) {
this.slug = this.compileSlug(newLabel)
}
},
methods: {
compileSlug: function(input) {
return input.replace(/[^a-zA-Z0-9 -]/g, '')
.replace(/\s+/g, '-')
.toLowerCase();
},
setSlug: function (input) {
this.slug = this.compileSlug(input.target.value)
}
}
});
JSFiddle functioning example

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