I am trying to follow the symfony2 dynamic form modification tutorial at http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#cookbook-form-events-submitted-data
My schema is a bit different to the one which they use in their tutorial. Mine consists of the following relationships:
Country (one to many) Office
Office (one to many) Employee
When I edit an existing employee, I would like it to load the Country where the Office is located as the default option, in addition to only showing the offices within that country in the Office dropdown (unless another country is selected, then the jQuery code (not included) should change that accordingly).
The result, however; is that the Country field still shows the placeholder value instead of the correct country for the Employee's Office. (On the plus side, the Office dropdown only shows the offices for that country, which means the $country->getOffices() call is working so I am working with the correct Country object, I just can't seem to have it selected by default).
Am I following the best practice here? Is there something I'm missing which isn't letting me set values in the form for related Entities?
Code:
class EmployeeType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('country', EntityType::class, array(
'class' => 'AppBundle:Country',
'mapped' => false,
'placeholder' => '=== Select a Country ===',
))
;
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) {
$form = $event->getForm();
// This will be the Employee entity
$data = $event->getData();
$office = $data->getOffice();
$country = null === $office ? array() : $office->getCountry();
$form->get('country')->setData($country); // I think this is what's not working properly.
$form->add('office', EntityType::class, array(
'class' => 'AppBundle:Office',
'placeholder' => '=== Select an Office ===',
'choices' => $country->getOffices(),
));
}
);
}
I had a chance to quickly read the tutorial link you reference, and I think you are right as to where the error is occurring.
I think (but I'm not sure), that this might the fix:
$office = $data->getOffice();
$offices = null === $office ? array() : $office->getCountry()->getOffices();
$form->add('office', EntityType::class, array(
'class' => 'AppBundle:Office',
'placeholder' => '=== Select an Office ===',
'choices' => $offices,
));
I only show the relevant sections which you need to change. Try it out and see if that helps.
Try to modify the data on the event instead:
class EmployeeType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('country', EntityType::class, array(
'class' => 'AppBundle:Country',
'mapped' => false,
'placeholder' => '=== Select a Country ===',
))
;
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) {
$form = $event->getForm();
// This will be the Employee entity
$data = $event->getData();
$office = $data->getOffice();
// since the country field is not set as "multiple"
// the data should not be an array but a string
$country = null === $office ? '' : $office->getCountry();
$data['country'] = $country(->getName()?);
$event->setData($data); // may work
$form->add('office', EntityType::class, array(
'class' => 'AppBundle:Office',
'placeholder' => '=== Select an Office ===',
'choices' => $country->getOffices(),
));
}
);
}
}
Related
I'm trying to build a form in Symfony 3 and am stuck with some issue which I think should be trivial to solve, but I guess I'm not looking at the correct place.
I have 2 objects, Entity and Supplier. 1 entity does have 1 supplier associated to it. 1 supplier can have many entities related to it. (One to Many association).
I'm trying to build the form for the Entity class, with a drop down list providing the supplier it has to be associated with.
Here is the Entity form class:
<?php
namespace VP\SupplierBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use VP\SupplierBundle\Entity\Supplier;
use VP\SupplierBundle\Repository\SupplierRepository;
class EntityType extends AbstractType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name')
->add('supplier', Supplier::class, array(
'class' => 'VPSupplierBundle:Supplier',
'property' => 'name',
'query_builder' => function (\Doctrine\ORM\EntityRepository $er) {
return $er->findAllByIsDeleted(0);
},
'choice_label' => 'name'
))
->add('submitnew', SubmitType::class, array(
'label' => 'Add Entity',
'attr' => array(
'class' => 'btn btn-primary',
)));
}
Here is my SupplierRepository which is supposed to be called:
<?php
namespace VP\SupplierBundle\Repository;
use Doctrine\ORM\EntityRepository;
/**
* SupplierRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class SupplierRepository extends EntityRepository {
public function findAllByIsDeleted($isDeleted) {
$qb = $this->createQueryBuilder('s');
$qb
->where('s.isDeleted = :isDeleted')
->setParameter('isDeleted', $isDeleted);
return $qb;
}
}
Still, I get the following error:
Could not load type "VP\SupplierBundle\Entity\Supplier"
Any idea where it could come from? Tried to look at the official symfony doc and some forum topics but no luck so far...
Thanks a lot for your help!
Working solution provided by #Jeet
//
use Symfony\Bridge\Doctrine\Form\Type\EntityType as DoctrineEntityType;
//
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name')
->add('supplier', DoctrineEntityType::class, array(
'class' => 'VPSupplierBundle:Supplier',
'query_builder' => function (EntityRepository $er) {
return $er->findAllByIsDeleted(0);
},
'choice_label' => 'name'
))
->add('submitnew', SubmitType::class, array(
'label' => 'Add Entity',
'attr' => array(
'class' => 'btn btn-primary',
)));
}
Thanks!
You are doing it here wrong :
->add('supplier', Supplier::class, array(
'class' => 'VPSupplierBundle:Supplier',
'property' => 'name',
'query_builder' => function (\Doctrine\ORM\EntityRepository $er) {
return $er->findAllByIsDeleted(0);
},
'choice_label' => 'name'
))
You are loading a Supplier::class which unknown to Form Type. I guess you should instead load a EntityType::class form type.
Check here the document. You want to show a choice Field with association to entity, hence you need to have EntityType::class. To recognize the Entity Type, you are already providing your Entity identity through class option. So it makes clear.
Hope it helps!
I have the following form that contains data from the database it still WIP ( i'm missing a few fields that i didn't add yet).
The form loads data in the first select and based on that select i use ajax to populate a second select with options based on the first select ( basically the associations to the selected value). And from there again another select with certain options and so on and at the end when i submit the form i want to generate a report from database based on the data.
For the moment i'm stuck with the second field because i always get an error:
This value is not valid.
The form class:
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('survey', EntityType::class, [
'class' => SurveyManager::class,
'placeholder' => 'Choose option',
'attr' => [
'class' => 'field-change',
],
])
->add('headquarter', ChoiceType::class, [
'choices' => [],
])
->add('submit', SubmitType::class, [
'label' => 'Save',
])
;
}
I'm not reall sure how to fix the error or how should i handle this type of form. Can you help me out guys ?
Based on the answer i did this
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
$form->add('headquarter', EntityType::class, [
'class' => HeadQuarterManager::class,
'query_builder' => function(HeadQuarterManagerRepository $er) {
return $er->getHeadquarter($data['survey']);
},
]);
}
);
But i'm getting this error:
Notice: Undefined variable: data
Not really sure how to pass the data to the getHeadquarter method so i can return an array of id => name for the select.
When you run the function $form->isValid(), it checks against the form it built in the buildForm function. Any extra fields/value that aren't there will cause this error.
You can change this behaviour by using form events.
In the end this is how i did it:
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('survey', EntityType::class, [
'class' => SurveyManager::class,
'attr' => [
'class' => 'field-change',
],
])
->add('submit', SubmitType::class, [
])
->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
$modifier = $data['survey'];
$form->add('headquarter', EntityType::class, [
'class' => HeadQuarterManager::class,
'query_builder' => function (HeadQuarterManagerRepository $er) use ($modifier) {
return $er->getHeadquarter($modifier);
},
]);
}
);
}
Imagine this form
class CarType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('brand', EntityType::class, array(
'class' => 'AppBundle:CarBrand',
'label' => 'Brand',
))
->add('model', EntityType::class, array(
'class' => 'AppBundle:CarModel',
'label' => 'Car model',
'choices' => array(), // <- Depends on brand
));
// ...
}
}
I used some ajax to make the model list depending on brand selection.
I didn't use FormEvents because it was a real pain in the ass for my simple need (I have actually 3 dependencies based on multiple parameters and I already spent an entire day trying to figure out how to make it work with FormEvents)
Now, once the form is submitted, I want to get the bound values from my controller.
/**
* #Route("/context", name="context_selection")
*/
public function carSelectionAction(Request $request)
{
$form = $this->createForm(CarType::class, new Car());
$form->handleRequest($request);
if ($form->isSubmitted()) {
$car = $form->getData();
dump($car); // <- Getting only brand here
}
return $this->render('AppBundle::car-selection.html.twig', array(
'form' => $form->createView()));
}
The dumped data is showing only the CarBrand object bound, I imagine because the selected CarModel is not a valid choice.
How can I get the bound CarModel ?
I'd rather not having to read the request params and load the objects myself
I'm having dificulties trying to set on my form the selected values on the preferred choices.
I have a class calendar that can have multiple taxes:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('possibledigits')
->add('notes')
->add('autogenerateyear', 'checkbox', array('required' => false))
->add('calendartaxes', null, array(
'required' => false,
'multiple' => true,
'expanded' => true
))
;
}
The relation is many to many:
/**
* #ORM\ManyToMany(targetEntity="CalendarTax", indexBy="name", inversedBy="calendarios")
* #ORM\JoinTable(name="contable_schedule_taxes_relation")
*/
private $calendartaxes;
What I was going to do is to try to get the entity of the form and try to get the selected taxes. But I don't know if that is the correct way to do it.
Is there a more elegant way to do it?
I'm using symfony 2.3.
Regards.
I am assuming you have a relationship set up betwween Calendarios and CalendarTax
so before you create the form ... do
$calendarios = new CalendarIOS();
$calendartax = $this->getDoctrine()->getManager()->getRepository('NamBundle:CalendarTax')->find($calendar_tax_id);
$calendarios->addCalendarTax($calendartax);
now when you send the entity over to the formbuilder...you will have that perticular calendartax checked... :)
Finally I use the preferred_choices of the choice.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$calendario = $builder->getData();
$taxesId = array();
foreach($calendario->getCalendartaxes() as $tax){
$taxesId[] = $tax;
}
$builder
->add('name')
->add('possibledigits')
->add('notes')
->add('autogenerateyear', 'checkbox', array('required' => false))
->add('calendartaxes', null, array(
'required' => false,
'multiple' => true,
'expanded' => true,
'preferred_choices' => $taxesId,
))
;
}
Making one more query and getting the calendar of the form builder.
The comment given by #Hakim was the one that put me on the right path.
Thanks!
I want from the user to select a type of questionnaire, so I set a select that contains questionnaires types.
Types are loaded from a an entity QuestionType .
$builder
->add('questionType', 'entity', array(
'class' => 'QuizmooQuestionnaireBundle:QuestionType',
'property' => 'questionTypeName',
'multiple' => false,
'label' => 'Question Type'))
->add('type', 'hidden')
;
What am not able to achieve is to set a default value to the resulted select.
I have googled a lot but I got only preferred_choice solution which works only with arrays
I made it by setting a type in the newAction of my Controller I will get the seted type as default value.
public function newAction($id)
{
$entity = new RankingQuestion();
//getting values form database
$em = $this->getDoctrine()->getManager();
$type = $em->getRepository('QuizmooQuestionnaireBundle:QuestionType')->findBy(array('name'=>'Ranking Question'));
$entity->setQuestionType($type); // <- default value is set here
// Now in this form the default value for the select input will be 'Ranking Question'
$form = $this->createForm(new RankingQuestionType(), $entity);
return $this->render('QuizmooQuestionnaireBundle:RankingQuestion:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
'id_questionnaire' =>$id
));
}
You can use data attribute if you have a constant default value (http://symfony.com/doc/current/reference/forms/types/form.html)
but it wont be helpful if you are using the form to edit the entity ( not to create a new one )
If you are using the entity results to create a select menu then you can use preferred_choices.
The preferred choice(s) will be rendered at the top of the list as it says on the docs and so the first will technically be the default providing you don't add an empty value.
class MyFormType extends AbstractType{
public function __construct($foo){
$this->foo = $foo;
}
$builder
->add('questionType', 'entity', array(
'class' => 'QuizmooQuestionnaireBundle:QuestionType',
'property' => 'questionTypeName',
'multiple' => false,
'label' => 'Question Type'
'data' => $this->foo))
->add('type', 'hidden')
;
}
In controller
$this->createForm(new MyFormType($foo));
The accepted answer of setting in the model beforehand is a good one. However, I had a situation where I needed a default value for a certain field of each object in a collection type. The collection has the allow_add and allow_remove options enabled, so I can't pre-instantiate the values in the collection because I don't know how many objects the client will request. So I used the empty_data option with the primary key of the desired default object, like so:
class MyChildType
extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('optionalField', 'entity', array(
'class' => 'MyBundle:MyEntity',
// Symfony appears to convert this ID into the entity correctly!
'empty_data' => MyEntity::DEFAULT_ID,
'required' => false,
));
}
}
class MyParentType
extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('children', 'collection', array(
'type' => new MyChildType(),
'allow_add' => true
'allow_delete' => true,
'prototype' => true, // client can add as many as it wants
));
}
}
Set a default value on the member variable inside your entity (QuestionType), e.g.
/**
* default the numOfCourses to 10
*
* #var integer
*/
private $numCourses = 10;