Symfony - Add and persist extra fields - php

I have a ChoiceType::Type field that display some choices, and i want to add, for each choices, an input to add a price on it. I did it like this :
->add('product_price', ChoiceType::class, array(
'choices' => array(
"Product 1",
"Product 2",
),
)
The JS that add the input for each choices :
var productBoxes = $("[id^=product_]");
// Listen the checkbox to display or hide the prices inputs
productBoxes.each(function (index) {
var priceField = '<label class="control-label required" for="product_price_' + index + '">Capacité</label>' +
'<input type="text" id="product_price_' + index + '" name="product[price][]" class="form-control">';
$(this).click(function () {
if ($(this).is(':checked')) {
$(this).parent().append(priceField);
}
})
})
The javascript works, it append fields next to each choices. Now I want to send my data in an array, like this :
["Product 1" => "value of the attached field"]
But I don't know how to fetch that extra data and save it to the database.
Did someone knows how to do it ?
EDIT 1
I tried to do it with CollectionType, but don't find out how to render each CollectionType element as checkbox. Is there a way to do it this way ?
Thanks for your help !

I think better way is to create custom type and set additional field there.
For example:
You main form type:
$builder->add('product_price', CollectionType::class, array(
'label' => 'Prices',
'entry_type' => ProductPriceType::class
));
And ProductPriceType:
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('product', TextType::class, array())
->add('price', NumericType::class, array());
}
/**
* #param OptionsResolver #resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\SomeEntity'
));
}
I think you get product data from base.
In this case you get array that contains 2 values - product and price

In your example, the easiest way would be to add two more fields to your form. Keep them hidden with CSS (display: none), and just show them with JS (toggle class "hidden" when the choice gets un/selected)
->add('product_one_price', NumberType::class, array(
'attr' => array('class' => 'hidden')
))
->add('product_two_price', NumberType::class, array(
'attr' => array('class' => 'hidden')
))
An alternative would be to have nested forms, or to build the form dynamically which may or may not be overkill, depending on what you're actually doing

Maybe I'm wrong but I think you should dig in the ChoiceType class.
When using the basic ChoiceType, Symfony docs says :
The choices option is an array, where the array key is the item's label and the array value is the item's value
If I correctly understand your case, you want something very specific like this kind of choices :
$choices = [
'item_label' => [
'value' => 'item_value',
'price' => 'item_price'
],
'item_label2' => [
'value' => 'item_value2',
'price' => 'item_price2'
],
...
]
I can't tell you precisely which class to override but your best bet will be to take a look at :
ChoiceListFactory class
ChoiceList class (the ChoiceType uses the child class SimpleChoiceList)
ChoiceToValuesTransformer
I have two questions :
What is the data model to store the label and the price ?
What is this list for ? If it's too complicated for you to dig in the Form components, maybe you should split your process in two steps :
One form to define your prices for your product
One form to select the products you are interested in

Related

How to use unmapped field in Symfony form to adjust data post submit?

I have two fields in my entity contractLength and contractEndDate. The aim is to be able to specify EITHER a contractLength OR a contractEndDate.
To accomplish this I have added an unmapped field to the form useBespokeEndDate, of checkbox type.
I want to be able in the form class ContractType to listen for the submit event and then check if the checkbox is ticked, if it is then proceed as normal but if it is not ticked (so we are using contractLength) then set the contractEndDate field to null.
I believe I should be using Event Listeners as detailed here http://symfony.com/doc/master/form/dynamic_form_modification.html#customizing-your-form-based-on-the-underlying-data (under the Dynamic Generation for Submitted Forms heading) to listen for the POST_SUBMIT event and then check the value of useBespokeEndDate and then modify the data as appropriate. However, I'm confused as to how exactly to get the form data for the unmapped field and then to also modify the actual data of the form.
My buildForm method in ContractType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('useBespokeEndDate', CheckboxType::class, array(
'label' => 'Use Bespoke Contract End Date?',
'empty_data' => false,
'mapped' => false
))
->add('contractLength', ChoiceType::class, array(
'choices' => array(12 => 12, 24 => 24, 36 => 36, 48 => 48, 60 => 60),
'label' => 'Contract Length (months)'
))
->add('contractEndDate', DateType::class, array('label' => 'Contract End Date'))
->add('save', SubmitType::class, array('label' => 'Save'));
$builder->addEventListener(FormEvents::POST_SUBMIT,
// Do something here to modify the data
);
}
Had the same problem, and I could access the unmapped values this way :
// in your POST_SUBMIT callback :
$form = $event->getForm();
$contract = $event->getData();
// get the unmapped value :
$useBespokeEndDate = $form->get('useBespokeEndDate')->getData();
// .. continue
I've got a similar situation where I have an option field, it's use (and therefore value) depending on a radio button group.
Rather than making a dynamic form, I instead separated the form from the entity, and actually ended up created a sub-form (Type) that had its own data_class Form configuration option and Data Mappers to convert the value objects to the entities.
There are useful blog posts with more information about Value Objects in forms and Creating a Custom Data Mapper for Symfony Forms.
For me, this alos gives the advantage of moving some quite complex checks into their own smaller class, and making the code more reusable.

How can I edit a field of a form in my controller?

I'm having trouble formulating a solution for 'editing' a field of my form in my controller.
Here's what I have:
I have a symfony2 form registered as a service that I call in a function in my controller. I am removing a bunch of fields that aren't necessary for this other form I am directing my users to and then adding a few others.
(I realize I could create another form and create another service and such but for my purpose this would be a bit overkill. I'm doing it this way because the form functions the same, however some fields are not needed and a few new specific ones are.)
I would now like to essentially 'edit' one field in this form... The 'occupation' field. This field is a choice field acting as radio buttons populated by an array of choices. It's required and has no empty_value requirement in its original state.
I would like to edit it in my controller function to have the same exact values however with a required value of false and an empty_value of null.
With the commented out code below the result is a dissapearance of the occupation field in my 'new' form and it is replaced by an empty drop down. I realize it's because I'm overriding the whole field below, but I cannot figure out how to simply edit it.
Code:
/**
* Explanation of addAndRemoveFieldsInRegisterForm function:
* The function gets the 'registration' form and removes any
* fields not needed for the 'in_registration' form
* and then adds the necessary fields to the form.
*/
private function addAndRemoveFieldsInRegisterForm($user)
{
$form = $this->createForm('user_registration', $user);
// http://stackoverflow.com/questions/10920006/pass-custom-options-to-a-symfony2-form ---
// --- use that to help. Look at changing the value of array.
$form->remove('title');
$form->remove('company');
$form->remove('username');
$form->remove('city');
$form->remove('state');
$form->remove('country');
$form->remove('gender');
$form->remove('age');
$form->remove('roles');
// $form->remove('occupation');
// $pr = $form->get('occupation');
// $pr->set('required' => false);
// $form->get('occupation')->add('required'=>false, 'empty_value'=>null);
// $form->add('occupation','choice', array(
// 'required' => false,
// 'empty_value' => null,
// ));
// echo "<pre>";
// var_dump(get_class_methods($form));die;
$form->add('occupation','choice', array(
'required' => false,
'empty_value' => null,
));
$form->add('canEmail', 'checkbox', array(
'label' => 'Can Email?',
'required' => false,
));
$form->add('sendEmail', 'choice', array(
'label' => 'Send Welcome Email? ',
'required' => true,
'mapped' => false,
'expanded' => true,
'choices' => array(
"yes" => "Yes",
"no" => "No"
),
));
return $form;
}
Original Form (the one that's used as a service)
private $requireOccupation;
$this->requireOccupation = true;
->add('occupation','choice', $options['occupation'])
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$occupation = array(
"label" => "Which of these currently describes you best? (Occupation):",
"expanded" => true,
'required'=> $this->requireOccupation,
"choices" => array(
"X" => "X",
"B" => "B",
"C" => "C",
"J" => "J",
),
'constraints' => array(
new NotBlank()
));
$resolver->setDefaults(array(
'occupation' => $occupation,
));
}
I think it is better to create another form. It can herit from your already defined form to change only the field you want
class SomeFormType extends OriginalFormType {
public function buildForm(FormBuilderInterface $builder, array $options) {
parent::buildForm($builder, $options);
$builder
->remove('someField')
->add('someField', 'choice', [
"expanded" => true,
"choices" => $yourArray
]);
}
It has the advantage to be mapped on different object
Firstly, I realize the way I wanted to solve this issue is odd when considering I could have created another form with either the fields I wanted to use or just the one field that needed to change and register the form as a service to use it elsewhere, but I was tasked to complete it this way.
Second, my solution is quite simple. I pass values into my form with a default value set in the form.
In the form:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$requireOccupation = true;
$emptyValue = null;
//whatever other values you want to set here
$resolver->setDefaults(array(
'requireOccupation' => $requireOccupation,
'emptyValue' => $emptyValue,
));
}
and then on the field's properties:
$builder->add('occupation', 'choice', array(
"label" => "Some sort of label",
"required" => $options['requireOccupation'],
"empty_value" => $options['emptyValue'],
...
));
Now in the controller:
$form = $this->createForm('registration', $user, array(
'requireOccupation' => false, 'emptyValue' => null
));
call that where you want to generate your form while passing in the values you want to use for that form.
I am by no means an expert on Symfony and this solution would probably generate some issue with those who are. But it works for me.

preferred_choices entity field form never run

I want to set a form with a "preferred_choices" on top of my select field HTML corresponding to previous submitted datas selected by the user. I want to construct an entity field with a constant list AND a preferred_choices top element if the form is previously submitted.
I never ran correctly this function in symfony2.
Can you help me to construct correctly my field form.
Why my preferred_choices options select nothing when the form is construct ?
I setting this with correct object setted previously in the code.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$defaultCQSsearch = new CqsProSansMarque();
$defaultCQSsearch->setRayLibelle((!array_key_exists('ray_libelle', $options['attr'])) ? null : $options['attr']['ray_libelle']);
$defaultCQSsearch->setFamLibelle((!array_key_exists('fam_libelle', $options['attr'])) ? null : $options['attr']['fam_libelle']);
$defaultCQSsearch->setCaeLibelle((!array_key_exists('cae_libelle', $options['attr'])) ? null : $options['attr']['cae_libelle']);
$builder
->add('ray_libelle', 'entity', array(
'class' => 'ApplicationDriveBundle:CqsProSansMarque',
'data_class' => 'Application\DriveBundle\Entity\CqsProSansMarque',
'property' => 'ray_libelle',
'query_builder' => function(CqsProSansMarqueRepository $er){
return $er->createQueryBuilder('a')
->select('a')
->groupBy('a.ray_libelle');
},
'preferred_choices' => array($defaultCQSsearch),
'label' => 'rayon',
'required' => false,
))
preferred_choices option expects an array of values but you are passing an array of object (i.e. $defaultCQSsearch)

Symfony2 Embed form values

I have my own utility CalculatorType for just calculating values for my document(I am using ODM), not reference for another document or sub arrays|object.
It has simple 2 inputs:
$builder
->add('price', 'text', array(
'label' => false,
'data' => isset($options['data']) ? $options['data']->getPrice() : '0.00'
))
->add('count', 'integer', array(
'label' => false,
'data' => isset($options['data']) ? $options['data']->getCount() : '10000'
));
In parent form I have:
$builder->
... // multiple fields
->add('calculator', 'calculator');
So when I am trying to save my form, I have an error:
Neither the property "calculator" nor one of the methods
To skip setting calculator field, I've added mapped => false to options
->add('calculator', 'calculator', array('mapped' => false));
and added eventlistener to transform calculator data
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
$data["price"] = $data["calculator"]["price"];
$data["count"] = $data["calculator"]["count"];
unset($data["calculator"]);
$event->setData($data);
});
Now form submits values but calculator fields not passing to embed form, because of unsetting $data['calculator']
If I comment unset($data["calculator"]); then I have an error
This form should not contain extra fields
So I can't find any way to make this form work. Any ideas?
Found mistake in my form, so there is option for such type of forms: http://symfony.com/doc/current/cookbook/form/inherit_data_option.html

Add attribute to form widget not work

I'm trying to add the selected attribute to my <option> tags
{% for product in form.products %}
{# of course this should only done inside a if,
but for testing purpose, it's okay #}
{{ form_widget(product, { 'attr': {'selected': 'selected'} }) }}
{% endfor %}
However this does not work. Even exactly the same copy&paste from the symfony2 docs here does not work: http://symfony.com/doc/current/book/forms.html#rendering-each-field-by-hand
I'm adding the form element inside a FormType à la:
public function buildForm(FormBuilderInterface $builder, array $options) {
parent::buildForm($builder, $options);
$builder
->add('products', 'entity', array('attr' => array('class' => 'choseable input-xlarge'),
'property_path' => false, 'label' => 'form.products.title', 'class' => 'Test\Bundle\Entity\Product', 'choices' => $products, 'multiple' => true, 'empty_value' => 'form.products.placeholder'));
}
All variables ($products) are ok in the example above.
Is there a problem?
I'm using Symfony 2.1.9-dev.
Can you update the question with form creation code? I'm pretty sure that Symfony is able to make item selected only if it's present in your model. But since you field is marked with property_path => false I don't think you can override this behavior...
An idea:
Fetch all the data used in your entity field type and pass it to form object (either via constructor or options array)
Add field as you're currently doing it
Invoke setData right afterwards to set field data (using data fetched in step #1)
Btw, I am not big fan of setData but situation like this just calls for it :)
Hope that this helps...
There are several issues with your code. I reformatted the code below for better clarity.
public function buildForm(FormBuilderInterface $builder, array $options) {
parent::buildForm($builder, $options);
$builder->add('products', 'entity', array(
'attr' => array('class' => 'choseable input-xlarge'),
'property_path' => false,
'label' => 'form.products.title',
'class' => 'Test\Bundle\Entity\Product',
'choices' => $products,
'multiple' => true,
'empty_value' => 'form.products.placeholder'
));
}
Don't use class inheritance for form types (except of course extending AbstractType). Use getParent() for extending other types instead.
You can't set attributes of <option> tags manually. You would have to render the tags by hand.
For marking choices as selected, you need to pass default data to the field. Usually getProducts() would be called here to get the defaults, but you disabled that by setting property_path to false. So you should set the defaults manually by using the data option. Note that the defaults need to be an array/a collection, since multiple is true.
You are manually passing choices, which is a bad practice. If you just remove this option, the entity field can itself query the choices from the DB and optimize the needed statements for better performance.
Format your code. Really. :P

Categories