symfony2 - adding choices from database - php

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

Related

Symfony - Multiple Fields for one Entity Attribute

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

How to get doctrine repository in form type in Symfony?

I am trying to use Doctrine in form type class.
Here is the Code
class UserType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$userRepository= $this->getDoctrine()->getRepository('UserBundle:User');
$user = $userRepository->findOneByName('Sara');
$builder
->setAction($options['data']['url'])
->setMethod('GET')
->add('user', 'text', array(
'user.id' => $user,
'label' => 'User',
))
->add('save', 'submit', array('label' => 'SoapServer'))
;
}
I am getting an error like this:
Attempted to call an undefined method named "getDoctrine" ...
Is there anyway to call doctrine?
I feel really ashamed about sharing this answer, but if you are desperate you can use something like this.
Please, try to avoid this solution and find something nicer, without any global variables.
Also, note that you have changed the repository var from $userRepository to $statusRepository.
public function buildForm(FormBuilderInterface $builder, array $options)
{
global $kernel;
if ( 'AppCache' == get_class($kernel) )
{
$kernel = $kernel->getKernel();
}
$doctrine = $kernel->getContainer()->get( 'doctrine' );
$userRepository=$doctrine->getRepository('UserBundle:User');
$user = $userRepository->findOneByName('Sara');

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

creating types on the fly for collection type

Is it possible to create FormType on the fly and avoid manually creating new form type class.
Currently I have to do this:
MyController.php
$builder = $this->createBuilder(new ParentEntity());
$builder
->add('parentfield1')
->add('parentfield2');
->add('children', 'collection', array('type' => new ChildType());
ChildType.php
class ChildType extends \Symfony\Component\Form\AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('childname1')
->add('childname2');
}
public function getDefaultOptions(array $options) {
return array(
'data_class' => 'ChildEntity',
);
}
public function getName() {
return 'childentity';
}
}
I would like to avoid creating ChildType class manually and do something like this instead:
MyController.php
$childBuilder = $this->createBuilder(new ChildEntity());
$childBuilder
->add('childfield1')
->add('childfield2');
// how to create FormType from builder???
$childType = $childBuilder->??????;
$builder = $this->createBuilder(new ParentEntity());
$builder
->add('parentfield1')
->add('parentfield2');
->add('children', 'collection', array('type' => $childType);
I tried to get the type with:
$childBuilder->->getFormConfig()->getType();
but that did not work. I get error:
The form's view data is expected to be an instance of class Namespace\Bundle\Entity\ChildEntity, but is an instance of class Doctrine\Common\Collections\ArrayCollection. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms an instance of class Doctrine\Common\Collections\ArrayCollection to an instance of Namespace\Bundle\Entity\ChildEntity.
Currently the best I came up with is to create custom FormType that accepts array of fields as constructor but is there a better way?

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