I have a form class with several ChoiceType fields that contain an array of options with a key:value pair. When the form is submitted the value is saved. However when I'm rendering the object I would like to show the Key value instead.
Example: 'Monthly' => '1month'. 1month is stored, prefer output to be Monthly.
I'm trying to avoid conditionals to check the value and changing the output to the key value.
I wasn't able to find any documentation about best practices for this sort of thing. I'm thinking about creating a service that stores all the choice options arrays and build a twig filter for changing the rendered output based on the array from the service.
Am I on the right track or is there an easier way?
I tried the service solution and got it working. I'm not sure if it is the most elegant or efficient way but it did the job. The form was a form class type and I injected a service that contained the choice arrays.
I created a choices.php class file inside my Form folder next to the formType file. It acts as a service where it returns the choices to the formType and a custom twig extension filter I created. The formType I had to set up as a service in order to inject the choices service.
/*choices.php*/
public function getChoices($choice)
{
$choices = array('paymentFrequency' => array('Monthly' => '1month',
'Bi-weekly' => '2weeks'),
'compounding' => array('Monthly' => 'monthly',
'Daily' => 'daily')
);
return $choices[$choice];
}
/*formType.php*/
->add('paymentFrequency', ChoiceType::class, array(
'label' => 'Payment Frequency:',
'choices' => $this->choicesService->getChoices('paymentFrequency'),
))
->add('compounding', ChoiceType::class, array(
'label' => 'Compounding:',
'choices' => $this->choicesService->getChoices('compounding'),
))
I then created a custom twig filter function where the choices service is injected into it.
/*twigExtension.php*/
public function renderChoicesFilter($value, $type)
{
$choices = $this->choicesService->getChoices($type);
return array_search($value, $choices);
}
/*twig template*/
{{ object.paymentFrequency|renderChoices('paymentFrequency') }}
You can create enumerator class and use it in your template, like so:
class MyChoicesEnum {
private static $choices = array(
'Monthly' => '1month',
'Quarterly' => '4month',
// etc...
);
public static function choices() {
return self::$choices;
}
}
Then you pass the class method result to template, in the returned array:
...
'form' => $form->createView()
'my_choices' => MyChoicesEnum::choices()
And in twig:
{{ my_choices.key }}
Related
I am adding fields to a project based on a project based on Symfony 2 and Sonata. I am trying to follow the instructions from this answer. In one of my admin classes, I have inserted the following code:
$default = 'Germany';
if (!$this->getUser()->hasRole(User::CONTENT_SUPPLIER)) {
$formMapper
->tab('Distribution')
->with('Distribution')
->add(
'module',
null,
[
'empty_data' => $default,
]
)
->add(
'distributions',
'distribution_list',
[
'label' => false,
'required' => 'false',
'disabled' => true
]
)
->add('plannedDistributions')
->end()
->end()
;
}
... and while I expect to see a reference to the "Germany" object by default in my form, I instead see an empty field. Should I be passing in an object rather than a string? Is what I'm trying to do even possible? What am I doing incorrectly here?
I think you missed a crucial bit in the documentation regarding empty_data:
This option determines what value the field will return when the submitted value is empty (or missing). It does not set an initial value if none is provided when the form is rendered in a view.
This means it helps you handling form submission with blank fields.
That means empty_data will populate your model with the data when the form was submitted without a default value.
I'm not familiar with the $formMapper used in your snippet, but in a typical Symfony-Controller you could create your form like this:
$form = $this->createForm(MyForm::class, $initialData);
In this case $initialData contains a property Distribution with the value Germany. Alternatively you could try providing the value in your frontend.
To set default data use option 'data'.
Sample:
//Use block
use Symfony\Component\Form\Extension\Core\Type\TextType;
//...
$formMapper
->add('module', TextType::class,[
'data' => 'Gearmany',
]);
I have a Symfony2 unmapped form which has a field of type 'Entity'. Here is the form type definition:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('RegistrationForgotEmailTemplate', 'entity', array(
'class' => 'EmailBundle:EmailTemplate',
'placeholder' => '-- Default ---',
));
}
Here is where I build the form instance:
$data = array('RegistrationForgotEmailTemplate' => 4);
$form = $this->createForm($formType, $data, array(
'action' => $url,
'method' => 'POST'
));
My problem is the form is not setting the RegistrationForgotEmailTemplate field to the correct entity (id 4). I assume this is because I am providing the id of the entity rather than an instance of it. Is there a way of providing the just the id of the entity to set this field or is the only way to pass an instance of the entity I want to set the field to?
You could either use a transformer or you could use the "choice" field rather than "entity" and inject an object to provide the choices list (likely a repository).
Personally I think you'll find the transformer method easier. The resulting code will be nicer too
I'm trying to dynamically generate a form based on user-provided field definitions. This is fairly straightforward for the basic form:
$builder = $app['form.factory']->createBuilder('form', $my_data);
foreach ($my_user_provided_field_definitions as $field) {
$builder->add($field->handle, $field->type, $field->options);
}
$form = $builder->getForm();
But I also want to have sub-forms using the collection field type, and while it is so easy to dynamically generate the top-level form as I've done above, it does not seem possible to dynamically generate a sub-form for the collection field because all of the docs and sample code I've come across utilize either a single field like so:
$builder->add('my field', 'collection', array(
'type' => 'text',
'allow_add' => true,
));
...or a custom "Type" object, like so:
$builer->add('my field', 'collection', array(
'type' => new TagType(),
'allow_add' => true,
));
The TagType class in the above example must be defined in code (see http://symfony.com/doc/master/cookbook/form/form_collections.html )... but my problem is that I cannot have the class defined in code because these sub-forms are dynamically generated based on user data -- so I do not know what fields the sub-form will contain until run-time!
Is there a way to dynamically generate the sub-forms that can then be passed in to the top-level form, or is this a use case that is not handled by the Symfony FormBuilder?
I am building an application using Symfony 2, and one of the pages contains a large form, which has a number of fixed fields but also a number of fields whose presence and number depends entirely on related data taken from the DB. More specifically, the form poses a number of questions, and the questions themselves come from the DB, having different text and IDs. The answers need to be stored in the DB, linked to the questions.
I've read up on how Symfony handles forms (Basic Symfony Forms), including the relationship between a Form object and the data object backing it, creating Fields from the Form object, and validating input according to rules applied to the Form object.
Since I don't want a fixed Form object I looked at how you're meant to dynamically modify Forms (Dynamic Symfony Forms), but the examples there seem to be about choosing whether to show certain pre-defined fields, or controlling the options in the fields, not completely generating new fields from scratch, which is what I want.
I had a look at the Symfony page on Data Transformers, and it seemed relevant, but I couldn't quite get my head around how it would help me. They seem more about mapping a Form object to an Entity than mapping complex form submissions to a Form object.
I did see this SO answer where the object backing the Form object is just an array, which also seemed relevant: I thought that maybe the answer was to generate some anonymous object with the dynamic fields in, and then make a form from that, but I don't see how I'd set up Symfony validation on it, and my form is not completely different each time, so doesn't need to be entirely anonymous.
What is the best way to achieve this using Symfony 2, ideally without giving up such framework benefits as easy validation etc?
EDIT I hadn't heard of Symfony Form Collections before, and it looks like this might do what I want - each Question in my case would map to an individual subform object. I'll update again if I try it out successfully.
Your question is pretty broad but this might help focus.
I have what I call a DynamicFormType which can add elements to a form based on a configuration items array.
class DynamicFormType extends AbstractType
{
protected $name;
protected $items;
public function getName() { return $this->name; }
public function __construct($name, $items)
{
$this->name = $name;
$this->items = $items;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
foreach($this->items as $name => $item)
{
switch($item['type'])
{
case 'radio':
$attr = array('class' => 'radio-medium');
$builder->add($name,'choice',array(
'label' => $item['label'],
'required' => false,
'attr' => $attr,
'empty_value' => false,
'expanded' => true,
'multiple' => false,
'choices' => $item['choices'],
));
break;
case 'select':
$attr = array();
$builder->add($name,'choice',array(
'label' => $item['label'],
'required' => false,
'attr' => $attr,
'empty_value' => false,
'expanded' => false,
'multiple' => false,
'choices' => $item['choices'],
));
break;
case 'text':
$attr = array();
if (isset($item['size'])) $attr['size'] = $item['size'];
$builder->add($name,'text',array(
'label' => $item['label'],
'required' => false,
'attr' => $attr,
));
break;
case 'textarea':
$attr = array();
if (isset($item['rows'])) $attr['rows'] = $item['rows'];
if (isset($item['cols'])) $attr['cols'] = $item['cols'];
$builder->add($name,'textarea',array(
'label' => $item['label'],
'required' => false,
'attr' => $attr,
));
break;
case 'collection':
$dynamicType = new DynamicFormType($name,$item['items']);
$builder->add($name,$dynamicType,array(
'label' => false,
'required' => false,
));
break;
}
}
return; if ($options);
}
}
I trust you can see how the items array is used to create form elements. In my application I use this with:
$basicType = new DynamicFormType('basic',$project->getPlan());
$builder->add('basic',$basicType, array('label' => false));
$project->getPlan() returns the items array which describes the various elements I want to add.
Hopefully, you can start with something like this and figure out what elements need to be added from your data structure. I didn't need validation here but it should be easy enough to add constraint information to you elements once you get things working.
OK, in the end what I wanted wasn't a truly dynamic form (but thanks to #Cerad for that code), but one using Collections where the number of certain child form items was variable.
So I essentially followed the Symfony Docs about Collections
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