Symfony - Multiple Fields for one Entity Attribute - php

I have three three select fields for one entity attribute. As the picture below shows.
Is there a way to detect which of the select fields is used; then get its value and map it with the corresponding attribute?
And is it possible to send parameters to a form type (in this example TestType , please see below). I am trying to make it generic and re-usable for other attributes.
Here is what I have up to now.
MyForm.php
<?php
namespace MyBundle\Form;
use MyBundle\Form\Type\TestType;
use ..etc
class MyForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class)
->add('D1', TestType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MyBundle\Entity\Project'
));
}
public function getBlockPrefix()
{
return 'mybundle_project';
}
}
TestType.php
<?php
namespace MyBundle\Form\Type;
use Sonata\AdminBundle\Form\Type\Filter\ChoiceType;
use ..etc
class TestType extends AbstractType
{
/*
* private $myArray1;
* private $myArray2;
* private $myArray3; numberOfSeletcs
* private $numberOfSeletcs;
Secondary Question: Is it possible to send these values as parameters?
public function __construct($array1, $array2, $array3, $n)
{
$this->myArray1= $array1;
$this->myArray2= $array2;
$this->myArray3= $array3;
$this->numberOfSeletcs= $n;
}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$myArray1 = array('label1'=>'','Value1'=>'Value1', 'Value2'=>'Value2','Value3'=>'Value3');
$myArray2 = array('label2'=>'', 'Value4'=>'Value4','Value5'=>'Value5');
$myArray3 = array('label3'=>'', 'Value6'=>'Value6','Value6'=>'Value6');
$builder
// ...
->add('H1', 'choice', array(
'choices' => $myArray1,
'choice_attr' => ['label1' => ['disabled selected hidden'=>'']]))
->add('H2', 'choice', array(
'choices' => $myArray2,
'choice_attr' => ['label2' => ['disabled selected hidden'=>'']]))
->add('H3', 'choice', array(
'choices' => $myArray3,
'choice_attr' => ['label3' => ['disabled selected hidden'=>'']]));
}
}
Thanks.

To detect which of the select fields is used you have to use Javascript. As you know Symfony is a PHP framework working on the server-side and to detect event on client-side javascript is needed. And for pass parameter to your form type you have the answer in this topic

Related

Symfony Forms, formtype label based on a property value of the data object

Is it possible to set the label value of a form type to be a property value that is available in the data object?
My FormType class:
class ShapeFractionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('value', NumberType::class, [
'label' => 'name'
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => ShapeFraction::class,
]);
}
}
The ShapeFraction data class:
class ShapeFraction
{
public string $name;
public string $type;
public float $value;
// ...
}
Rendering the form:
$shapeFraction = new ShapeFraction('A', 'length', 10);
$form = $formFactory->create(ShapeFractionType::class, $shapeFraction);
// Render using Twig...
The result is a simple form containing a single input with 'name' as label. Is it possible to use the property value of ShapeFraction::$name as the label of the field? Result will become like this: A: <input..>.
I would like to achieve this without using the object data directly in Twig or something.
Thanks in advance!
You can access the data that you pass to your form using $options['data'].
That means you can do this:
/** #var ShapeFraction $shapeFraction */
$shapeFraction= $options['data'];
$builder->add('value', NumberType::class, [
'label' => $shapeFraction->name
]);

Symfony 2.8/3.0 - Pass array to another formType from a collectionType

I could get this to work prior to v2.8 but as symfony now uses fully qualified class names name i'm unsure sure how to proceed.
I can pass an array (to populate a choice field) to a form without issue but if there is an another formType added via a collectionType how can a pass the array?
BTW - the array is gathered from data from a custom annotations - NOT an entity
Heres my code:
PageType.php
<?php
namespace Prototype\PageBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
const ActiveComponentsType = 'Prototype\PageBundle\Form\ActiveComponentsType';
const collectionType = 'Symfony\Component\Form\Extension\Core\Type\CollectionType';
class PageType extends AbstractType
{
private $cmsComponentArray;
public function __construct($cmsComponentArray = null)
{
$this->cmsComponentArray = $cmsComponentArray;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$cmsComponentArray = $options['cmsComponentArray'];
$componentChoices = array();
foreach($cmsComponentArray as $cmsComponent){
$componentChoices[$cmsComponent['name']] = $cmsComponent['route'];
}
//correct values are shown here
//print_r($componentChoices);
$builder
->add('title')
->add('parent')
->add('template')
->add('active')
->add('content')
->add('components', collectionType, array(
'entry_type' => ActiveComponentsType, // i want to pass $cmsComponentArray to ActiveComponentsType
'allow_add' => true,
'allow_delete' => true
))
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Prototype\PageBundle\Entity\Page',
'cmsComponentArray' => null
));
}
}
The ActiveComponentsType embeded form does work - except I'm unsure how to pass the $componentChoices array to it.
Any ideas?
The collection type defines the entry_options option which is used to configure the options that are passed to the embedded form type.

symfony2 - adding choices from database

I am looking to populate a choice box in symfony2 with values from a custom query. I have tried to simplify as much as possible.
Controller
class PageController extends Controller
{
public function indexAction()
{
$fields = $this->get('fields');
$countries = $fields->getCountries(); // returns a array of countries e.g. array('UK', 'France', 'etc')
$routeSetup = new RouteSetup(); // this is the entity
$routeSetup->setCountries($countries); // sets the array of countries
$chooseRouteForm = $this->createForm(new ChooseRouteForm(), $routeSetup);
return $this->render('ExampleBundle:Page:index.html.twig', array(
'form' => $chooseRouteForm->createView()
));
}
}
ChooseRouteForm
class ChooseRouteForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// errors... ideally I want this to fetch the items from the $routeSetup object
$builder->add('countries', 'choice', array(
'choices' => $this->routeSetup->getCountries()
));
}
public function getName()
{
return 'choose_route';
}
}
You could pass the choices to your form using..
$chooseRouteForm = $this->createForm(new ChooseRouteForm($routeSetup), $routeSetup);
Then in your form..
private $countries;
public function __construct(RouteSetup $routeSetup)
{
$this->countries = $routeSetup->getCountries();
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('countries', 'choice', array(
'choices' => $this->countries,
));
}
Updated (and improved) for 2.8+
Firstly you don't really need to pass in the countries as part of the route object unless they are going to be stored in the DB.
If storing the available countries in the DB then you can use an event listener. If not (or if you don't want to use a listener) you can add the countries in the options area.
Using Options
In the controller..
$chooseRouteForm = $this->createForm(
ChooseRouteForm::class,
// Or the full class name if using < php 5.5
$routeSetup,
array('countries' => $fields->getCountries())
);
And in your form..
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('countries', 'choice', array(
'choices' => $options['countries'],
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefault('countries', null)
->setRequired('countries')
->setAllowedTypes('countries', array('array'))
;
}
Using A Listener (If the countries array is available in the model)
In the controller..
$chooseRouteForm = $this->createForm(
ChooseRouteForm::class,
// Or the full class name if using < php 5.5
$routeSetup
);
And in your form..
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) {
$form = $event->getForm();
/** #var RouteSetup $routeSetup */
$routeSetup = $event->getData();
if (null === $routeSetup) {
throw new \Exception('RouteSetup must be injected into form');
}
$form
->add('countries', 'choice', array(
'choices' => $routeSetup->getCountries(),
))
;
})
;
}
I can't comment or downvote yet, so I'll just reply to Qoop's answer here:
What you proposed will work unless you start using your form type class as a service.
You should generally avoid adding data to your form type object through constructor.
Think of form type class like a Class - it's a kind of description of your form. When you make an instance of form (by building it) you get the Object of form that is build by the description in form type and then filled with data.
Take a look at this: http://www.youtube.com/watch?v=JAX13g5orwo - this situation is described around 31 minute of the presentaion.
You should use form event FormEvents::PRE_SET_DATA and manipulate fields when the form is injected with data.
See: http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#customizing-your-form-based-on-the-underlying-data
I got it working by calling getData on the builder
FormBuilderInterface $builder
// Controller
$myCountries = $this->myRepository->all(['continent' => 'Africa']);
$form = $this->createForm(CountriesType::class, $myCountries);
//FormType
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('pages', ChoiceType::class, [
'choices' => $builder->getData()
])
;
}

What is the best practice for building choices list

I have a form with a choice type element. I need to populate it with data. As I know there are 3 methods.
1. Controller:
// Controller
public function myAction()
{
$choices = ...; // create choices array
$form = $this->createForm(new MyFormType($dm), null, array(
'choices' => $choices,
));
}
// Form
class MyFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('cars', 'choice', array(
'choices' => $options['choices']
));
}
}
2. Form class + repository
// Controller
public function myAction()
{
$dm = $this->get('doctrine')->getManager();
$form = $this->createForm(new MyFormType($dm));
}
// Form
class MyFormType extends AbstractType
{
private $dm;
public function __construct($dm)
{
$this->dm = $dm;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('cars', 'choice', array(
'choices' => $options['choices']
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$list = array();
foreach($this->dm->getRepository('MyBundle:Cars')->findAll() as $car) {
$list[$car->getName()] = $car->getName();
}
$resolver->setDefaults(array(
'choices' => $list,
));
}
}
3. Form class + custom service
// Controller
public function myAction()
{
$dm = $this->get('doctrine')->getManager();
$form = $this->createForm(new MyFormType(), null, array(
'myservice' => $this->get('myservice'),
));
}
// Form
class MyFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('cars', 'choice', array(
'choices' => $options['myservice']->getCars()
));
}
}
// Service
class MyService
{
const ENTITY_CAR = 'MyBundle:Cars';
/** #var DocumentManager */
private $dm;
public function __construct(DocumentManager $dm)
{
$this->dm = $dm;
}
public function getCars()
{
return $this->dm->getRepository("MyBundle:Cars")->findAll();
}
}
I'll express my thoughts.
The 1st option is not the best practice. Especially when complicated logic is involved. Controllers should be as tiny as possible.
The 2nd is much better. But it exposes entity name and problems may occur if I decide to rename it.
The 3rd is the best option, imho. Entity names are concentrated in one place, better IDE type hinting, centralized entity management (search, save, remove...). The main disadvantage is a possible over-engineered class as it's becoming responsible for many read/write operations. On the other hand it can be divided into pieces.
What do you think about it?
The third option is good if you have to reuse that service elsewhere in your code (and if that service will grown in comparison of that you've wrote, we'll see it later). In that way, as you said, "manager" of that entity is one and contains itself the name of repo,a const, and so on.
BUT
If this service is use only as a "pusher" for reach your repository by hiding its name, I don't think that this solution is still much good as it seems.
Obviously if that service is thought for have multiple persistance options and multiple retrieve option (base on what ORM you've selected), in that case this could be the best practice.
In other cases, I suppose that the second one is always the better.
The first isn't practicable unless you want to ignore all good practices
I suggest a fourth solution : use an entity field as it is designed to be a choice field with options loaded from DB !
Here is the official doc http://symfony.com/doc/master/reference/forms/types/entity.html
And how you may use it :
// Form
class MyFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('cars', 'entity', array(
'class' => 'MyBundle:Cars',
'property' => 'name',
//Optionnal if you need to condition the selection
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('u')->orderBy('u.username', 'ASC');
},
));
}
}

Where do I set defaults for an associated entity in an embedded Symfony2 form?

I have an entity with a OneToOne association to another entity. For these purposes I'll call the initial entity "Parent" and the associated Entity "Child".
I have a Parent form working fine that embeds the child form and all the form elements for both entities appear, and I can save the data fine in the controller.
Now I want to set defaults for a number of attributes in the embedded doctrine entity. I could set values for the new entity in the controller, but the child entity is created in the embedded form class:
// Parent form
class Parent extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
//parent->add(...)
$builder->add('child', new Child(), array());
}
// In Child Form
class Child extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('visibilitycode', 'entity', array('label' => 'Visibility', 'class'=>'Acme\MyBundle\Entity\Visibility', 'property'=>'name'));
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Acme\MyBundle\Entity\Child',
);
}
Many of these defaults are for associated foreign keys, so if I was setting them in the controller I might use something like this:
$child->setVisibilityCode($em->getReference('AcmeMybundle:Visibility', 'P'));
Two solutions are offered to you (or maybe more :-) ):
initialize default values in Child entity itself ( in constructor for example)
use the empty_data option of the Form component:
class Child extends AbstractType {
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('visibilitycode', 'entity', array(
'label' => 'Visibility',
'class'=>'Acme\MyBundle\Entity\Visibility',
'property'=>'name'
));
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Acme\MyBundle\Entity\Child',
'empty_data' => function() use($visibility) {
$child = new Child();
$child->setVisibility($visibility);
return $child;
}
);
}
}

Categories