Symfony2: formbuilder : dynamically modify querybuilder - symfony

I am using the formbuilder to create a form as followed:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('content', 'textarea')
->add('rosters', 'entity', array(
'class' => 'PlatformBundle:team',
'property' => 'display',
'multiple' => true,
'expanded' => true,
'required' => true
))
->add('send', 'submit')
;
}
At the moment I get all "teams". I need to adapt the form to display certain teams depending of the request.
I can use the query-builder inside the form builder
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('content', 'textarea')
->add('rosters', 'entity', array(
'class' => 'PlatformBundle:team',
'property' => 'display',
'query_builder' => function(TeamRepository $t) use ($userId) {
return $r->createQueryBuilder('t')
->where('(t.user = :user')
},
'multiple' => true,
'expanded' => true,
'required' => true
))
->add('send', 'submit')
;
}
But the query changes for different questionnaire. In brief: always the same questionnaire but different teams to be listed (Am I making sense?).
Does someone has an idea how dynamically modify the querybuilder inside a formbuilder?

I suggest two possible alternatives.
If the request comes from the form itself (i.e. you already submitted the form with some data and want to refine the fields) you can access the submitted data like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$data = $builder->getData();
// now you can access form data
If the request comes from another source, you should use the "options" parameter. First, build a new $option for the requested user:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'user' => null,
));
}
Note: I set the default to null but you can set it to whatever you want.
After that you can pass the $option where you build the form, i.e.
// some controller
$option = array('user' => $request->get('user');
$teamForm = $this->createForm(new TeamType(), null, $options);
// ...

For those looking for an answer...
The best solution I found is create a variable in the formtype and to import it form the controller. My formType will look like that:
class formType extends AbstractType
{
// declare and construct the query in the class to use it in the function
private $qb;
public function __construct ($qb)
{
$this->qb = $qb;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// declare the variable within the function
$qb = $this->qb;
$builder
->add('content', 'textarea', array('required' => false))
->add('rosters', 'entity', array(
'class' => 'PlatformBundle:Team',
// use ($qb) -> $qb is query built in the controller (or repository)
'query_builder' => function(TeamRepository $r) use ($qb) {
return $qb;
},
'property' => 'display',
'multiple' => true,
'expanded' => true,
'required' => true
))
->add('send', 'submit');
}
In my controller I just pass $qb as an argument of the formtype
$qb = $this->getDoctrine()->getManager()->getRepository('PlatformBundle:Team')->qbteam($Id);
$form = $this->createForm(new formType($qb), $form);
with qbteam a function in the team repository which return the query (not a result).
public function qbteam($Id){
$qb = $this->createQueryBuilder('r')
->leftJoin('r.team', 'm')
->addSelect('m')
->where('m.user = :user')
->setParameter('user', $Id);
return $qb;
}
I hope it will help others.
cheers

Related

Symfony2 FormBuilder with Entity class

I have a form that works well, there is just one issue with it and I'm hoping that I'll get an answer on how to do what I need to do.
<?php
namespace ADS\UserBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Security\Core\SecurityContext;
class UserType extends AbstractType {
private $type;
public function __construct($type) {
$this->type = $type;
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('firstName', 'text', array('required' => true))
->add('lastName', 'text', array('required' => true));
$builder->add('email', 'email', array('required' => true));
$builder->add('parentCompany', 'entity', array(
'class' => 'ADSUserBundle:Company',
'expanded' => false,
'empty_value' => 'CHOOSE ONE',
'required' => false,
'property' => 'companyName'
))
->add('enabled', 'choice', array('choices' => array('1' => 'Enabled', '0' => 'Disabled')))
->add('roles', 'entity', array(
'class' => 'ADSUserBundle:Roles',
'required' => true,
'property' => 'displayName',
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array('data_class' => 'ADS\UserBundle\Entity\User'));
}
public function getName() { return 'ads_userbundle_user'; }
}
I have this form, the portion I am looking at is the 'roles' portion... Right now it created a multiple select box ( as I expect it to ), though the value is sequentially ie: 0,1,2,3,4...
What I really need is to figure out how to take this entity, and make the property to be the displayName ( as it is now ) and get the value to be the corresponding internalName This way it'll give me an array like:
array('ROLE_EXAMPLE' => 'EXAMPLE', 'ROLE_EXAMPLE1' => 'EXAMPLE1')
Any ideas how to accomplish this is greatly appreciated.
Kamil Adryjanek is correct, it is going to be much easier if you change it from an entity to a choice field. I've done some testing, both with FOSUserBundle and without the bundle - in both cases I hit some interesting road blocks.
First, I tried to run it through QueryBuilder in a repository, that didn't work out as it should have. The reason being, the fact that you wanted to be returning an array instead of a ORM object causes an error.
So next, I started looking at creating the choice field. All the guides, say to use the fieldname role instead of roles so I tried that, but I then had to duplicate the UserInterface from FOSUserBundle - I didn't want to do that -- so here I am stressed, and trying to figure it out.
Here is what I ended up doing, which works well.
private $normalRoles = array();
then in the __construct I add: $this->normalRoles = $roles;
Here is the builder:
$builder
->add('roles', 'choice', array(
'multiple' => true,
'choices' => $this->normalRoles
))
;
Originally, I left the multiple part out, figuring that it'd at least let me see an option box. I ended up getting an Array to String conversion error. So, adding the 'multiple' => true in, fixes that error.
Then, in my repository I created a function called normalizeRoles
public function normalizeRoles() {
$data = array();
$qb = $this->getEntityManager();
$query = $qb->createQuery(
"SELECT r.internalName, r.displayName FROM AcmeUserBundle:Roles r"
)->getArrayResult();
foreach ($query as $k => $v) {
$data[$v['internalName']] = $v['displayName'];
}
return $data;
}
From here, we just have to make some small edits in the DefaultController of the UserBundle in the newAction and editAction ( both are the same changes )
So, first off is to put into your Controller use Acme/UserBundle/Entity/Roles in order to avoid any errors and be able to get that repository.
Next, right before you create the form you run the normalizeRoles() function
$roles = $em->getRepository('AcmeUserBundle:Roles')->normalizeRoles()
Then, you pass it through the construct via: new UserType($roles)
full line for that would look like this:
$form = $this->createForm(new UserType($roles), $entity, array(
'action' => $this->generateUrl('acmd.user.edit', array(
'id' => $id)
)
));
or for new:
$form = $this->createForm(new UserType($roles), $entity, array(
'action' => $this->generateUrl('acmd.user.new')
)
));
At this point -- You'll have a working system that will allow you to dynamically add roles into a database table, and then associate those with a new or current user.
You can try do it via query_builder attribute:
$builder->add('roles', 'entity', array(
'class' => 'ADSUserBundle:Roles',
'required' => true,
'property' => 'displayName',
'query_builder' => function (RolesRepository $queryBuilder) {
return $queryBuilder->someMethod() // some method in this repository that return correct query to db.
},
));
In this case it would be better to use choice field Type (http://symfony.com/doc/current/reference/forms/types/choice.html) instead of entity and pass some role choices as option to form because entity field Type get entity id as key for choices:
public function buildForm(FormBuilderInterface $builder, array $options) {
...
$builder->add('roles', 'choice', array(
'choices' => $options['role_choices']
));
...
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'ADS\UserBundle\Entity\User',
'role_choices' => array()
));
}
Notice: it's recommended to pass variables to form through options parameter, not in constructor.
if I understand your question correctly, you need a data transformers. They help you to show data in form as you want.
Documentation: http://symfony.com/doc/current/cookbook/form/data_transformers.html

Calling $builder->getData() from within a nested form always returns NULL

I'm trying to get data stored in a nested form but when calling $builder->getData() I'm always getting NULL.
Does anyone knows what how one should get the data inside a nested form?
Here's the ParentFormType.php:
class ParentFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('files', 'collection', array(
'type' => new FileType(),
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'by_reference' => false
);
}
}
FileType.php
class FileType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Each one of bellow calls returns NULL
print_r($builder->getData());
print_r($builder->getForm()->getData());
die();
$builder->add('file', 'file', array(
'required' => false,
'file_path' => 'file',
'label' => 'Select a file to be uploaded',
'constraints' => array(
new File(array(
'maxSize' => '1024k',
))
))
);
}
public function setDefaultOptions( \Symfony\Component\OptionsResolver\OptionsResolverInterface $resolver )
{
return $resolver->setDefaults( array() );
}
public function getName()
{
return 'FileType';
}
}
Thanks!
You need to use the FormEvents::POST_SET_DATA to get the form object :
$builder->addEventListener(FormEvents::POST_SET_DATA, function ($event) {
$builder = $event->getForm(); // The FormBuilder
$entity = $event->getData(); // The Form Object
// Do whatever you want here!
});
It's a (very annoying..) known issue:
https://github.com/symfony/symfony/issues/5694
Since it works fine for simple form but not for compound form. From documentation (see http://symfony.com/doc/master/form/dynamic_form_modification.html), you must do:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$product = $event->getData();
$form = $event->getForm();
// check if the Product object is "new"
// If no data is passed to the form, the data is "null".
// This should be considered a new "Product"
if (!$product || null === $product->getId()) {
$form->add('name', TextType::class);
}
});
The form is built before data is bound (that is, the bound data is not available at the time that AbstractType::buildForm() is called)
If you want to dynamically build your form based on the bound data, you'll need to use events
http://symfony.com/doc/2.3/cookbook/form/dynamic_form_modification.html

Build a form having a checkbox for each entity in a doctrine collection

I'm displaying an html table for a filtered collection of entities and I want to display a checkbox in each row as part of a form which will add the selected entities to a session var.
I'm thinking that each checkbox should have the entity id as its value and I'll get an array of ids from the form field data (ok, so the value ought to be an indirect ref to the entity, but for the sake of simplicity).
I've tried creating a form Type with a single entity type field, mapped to the id property of the entity and embedded into another form Type which has a collection type field.
class FooEntitySelectByIdentityType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('foo_id', 'entity', array(
'required' => false,
'class' => 'MeMyBundle:FooEntity',
'property' => 'id',
'multiple' => true,
'expanded' => true
));
}
# ...
and
class FooEntitySelectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('identity', 'collection', array(
'type' => new FooEntitySelectByIdentityType,
'options' => array(
'required' => false,
'multiple' => true,
'expanded' => true,
'attr' => array('class' => 'foo')
),
));
}
# ...
and in a controller the form is created with a collection of entities as the initial data
$form = $this
->createForm(
new \Me\MyBundle\Form\Type\FooEntitySelectionType,
$collection_of_foo
)
->createView()
;
When the form is rendered there is a single label for the identity field, but no widgets.
Is it even possible to use entity and collection type fields in this particular way?
If so, what might I be doing wrong?
I think this will answer your question.
Forget the FooEntitySelectionType. Add a property_path field option to FooEntitySelectByIdentityType and set the data_class option to null:
class FooEntitySelectByIdentityType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('foo_id', 'entity', array(
'required' => false,
'class' => 'MeMyBundle:FooEntity',
'property' => 'id',
'property_path' => '[id]', # in square brackets!
'multiple' => true,
'expanded' => true
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => null,
'csrf_protection' => false
));
}
# ...
and in your controller, build the FooEntitySelectByIdentityType:
$form = $this
->createForm(
new \Me\MyBundle\Form\Type\FooEntitySelectByIdentityType,
$collection_of_foo
)
->createView()
;
and then in the controller action which receives the POSTed data:
$form = $this
->createForm(new \Me\MyBundle\Form\Type\FooEntitySelectByIdentityType)
;
$form->bind($request);
if ($form->isValid()) {
$data = $form->getData();
$ids = array();
foreach ($data['foo_id'] as $entity) {
$ids[] = $entity->getId();
}
$request->getSession()->set('admin/foo_list/batch', $ids);
}
and finally, in your twig template:
{# ... #}
{% for entity in foo_entity_collection %}
{# ... #}
{{ form_widget(form.foo_id[entity.id]) }}
{# ... #}
If someone is looking for solution for Symfony >=2.3
You have to change this:
$data = $form->getData();
$ids = array();
foreach ($data['foo_id'] as $entity) {
$ids[] = $entity->getId();
}
to this:
$data = $form['foo_id']->getData();
$ids = array();
foreach ($data as $entity) {
$ids[] = $entity->getId();
}
I create a symfony bundle for render a collection of entities as a checkbox table in a configurable way. Although it has been a while since this question was asked, I hope this can help others with this same kind of problem.

Adding custom values in addition to contents from database in symfony2

My problem is as follows.
I have a drop down list in form builder which successfully retrieves the data from the database.
public function buildForm(FormBuilder $builder, array $options) {
$builder->add('Statusname', 'entity', array('empty_value' => 'All','class' => 'MyProject\EntityBundle\Entity\IssueStatusType', 'property' => 'name', 'required' => false,'query_builder' => function ($repository) { return $repository->createQueryBuilder('es')->orderBy('es.name', 'ASC'); },))
}
It works fine.
But when I add my custom data
'not closed' => 'Not closed'
into the drop down list i.e
public function buildForm(FormBuilder $builder, array $options) {
$builder->add('Statusname', 'entity', array('empty_value' => 'All','not closed' => 'Not closed','class' => 'MyProject\EntityBundle\Entity\IssueStatusType', 'property' => 'name', 'required' => false,'query_builder' => function ($repository) { return $repository->createQueryBuilder('es')->orderBy('es.name', 'ASC'); },))
}
it does not work. can some one tell me why?
Thanks in advance.
The third parameters for FormBuilder::add() method is an asoociative array of options.
'not closed' is not a valid option so it does not work.
In your case you have to create your custom collection by hand and use the 'choice' type.
In order to make it work you have to inject the entity manager to your form type.
This is a minimalist example:
class IssueType extends AbstractType
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('Statusname', 'choice', array(
'empty_value' => 'All',
'required' => false,
'choices' => $this->buildStatusNames(),
)
}
private function buildStatusNames()
{
$choices = array();
$types = $this
->entityManager
->getRepository('MyProject\EntityBundle\Entity\IssueStatusType')
->createQueryBuilder('es')
->orderBy('es.name', 'ASC')
->getQuery()
->getResult();
foreach ($types as $type) {
// I assume key is retrieved by getId
$choices[$type->getId()] = $type->getName();
}
$choices['not closed'] = 'Not closed';
return $choices;
}
}
entity relationships are managed within the entity, here you are building a form for a view which will contain id's and readable names for your users.
When the form is submitted, grab the object using the id as JF Simon mentions above and submit, provided you have set everything up correctly in your entities, Symfony will take care of the rest.

Symfony2 : Form View - add another field on entity field type

I have the following code in my buildForm method of my FormType
$builder->add('privileges', 'entity', array(
'label' => 'Privileges',
'expanded' => true,
'multiple' => true,
'class' => 'AcmeStoreBundle:AdminPrivilege',
'property'=> 'description',
'query_builder' => function(EntityRepository $er) use ($category)
{
return $er->createQueryBuilder('p')
->where('p.categoryid = :categoryID')
->andWhere('p.parentid = -1')
->setParameter('categoryID', $category->getId())
->orderBy('p.position', 'ASC');
}
));
Here if the parentid is greater than -1, then i'd like to show further form components after the checkbox where parentid is greater than -1 is created.
I've searched over Google and have been unable to find a way to do this, can anybody help?
Mat.
If I understand correctly, you can inject parentid and entity manager to form type construct from controller.
So you can run query before add field to builder, and use if-else. For example:
public function __construct($parentId, $em)
{
$this->parentId = $parentId;
$this->em = $em;
}
public function buildForm(FormBuilder $builder, array $options)
{
$choices = $this->em->getRepository()->callNeededMethod();
if($this->parentId){
$builder->add([someFieldParams]);
}else{
$builder->add([anoutherFieldParams]);
}
}

Resources