My form is on the base of self-referenced entity (category have parent category)
For the parent field, I want to have different query_builder function depending of action name. When action is update, parent field (drop-down list) contains all categories, excepting edited category. This is work good. But, when action is Create new, parent form field contains only null value (in my case Main category).
This is category form class
CategoryType class:
namespace Dimas\CatalogBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Doctrine\ORM\EntityRepository;
class CategoryType extends AbstractType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name')
->add('parent', 'entity', array(
'label' => 'Parent Category ',
'empty_value' => '- Main level -',
'class' => 'DimasCatalogBundle:Category', 'property' => 'getTreeName',
'required' => false,
'query_builder' => function(EntityRepository $er) use ($options) {
$er->createQueryBuilder('u');
//need only if action is 'update'
$er->where('u.id <> :selfId');
$er->setParameter(':selfId', $options['data']->getId());
// end if
return $er;
},
));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'Dimas\CatalogBundle\Entity\Category'
));
}
/**
* #return string
*/
public function getName() {
return 'dimas_catalogbundle_category';
}
}
Is it a good idea to add if in query_builder? How to get action name?
$options array contains action name, but it is so deep inside.
Pass in an option into the builder from your controller action that can act as a conditional:
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
if ($options['action'] === 'update') {
$callable = function (EntityRepository $er)...
} elseif ($option['action'] === 'Create new') {
$callable = function (EntityRepository $er) ...
}
...
'required' => false,
'query_builder' => $callable,
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver
->setDefaults(array(
'data_class' => 'Dimas\CatalogBundle\Entity\Category'
))
->setRequired(['action'])
;
}
// Your controller
public function createNewAction(Request $request)
{
$form = $this->createForm(new CategoryType(), $category, ['action' => 'Create new']);
}
public function updateAction(Request $request)
{
$form = $this->createForm(new CategoryType(), $category, ['action' => 'update']);
}
I achieve result this way:
I use EventListener to my form class. Learned from cookbook
Now my builForm function in CategoryType class looks like that:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('name');
$builder->addEventListener(FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($options) {
$category = $event->getData();
$form = $event->getForm();
// check if the Category object is "new"
// If no data is passed to the form, the data is "null".
// This should be considered a new "Category"
if (!$category || null === $category->getId()) {
$form->add('parent', 'entity', array(
'label' => 'Parent Category ',
'empty_value' => '- Main level -',
'class' => 'DimasCatalogBundle:Category', 'property' => 'getTreeName',
'required' => false,
'query_builder' => function(EntityRepository $er) use ($options) {
return $er->createQueryBuilder('u');
},
));
} else {
$form->add('parent', 'entity', array(
'label' => 'Parent Category ',
'empty_value' => '- Main level -',
'class' => 'DimasCatalogBundle:Category', 'property' => 'getTreeName',
'required' => false,
'query_builder' => function(EntityRepository $er) use ($options) {
return $er->createQueryBuilder('u')
->where('u.id <> :selfId')
->setParameter(':selfId', $options['data']->getId());
},
));
}
});
}
I did not get action (but I still wont).
Related
I faced up with some non-ordinary situation for me.
1) I have a dependent list that rendering by Symfony FormType like this:
2) Location and Instruction fields are depend from Company field.
3) When I change Company field (onchange event js) then goes ajax request that retrieves data from the database and build a dropdown list.
4) But when form is submitted I have an error:
Please help me to resolve this. Thanks in advance.
My formType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('hours')
->add('messageText', null, ['required' => false])
->add('company', null, [
'choice_label' => 'name',
'placeholder' => 'Please select company',
'required' => true
])
->add('procedure', TextType::class, ['attr' => ['placeholder' => 'Please type code or description'] ])
->add('instruction', ChoiceType::class, ['mapped' => false])
->add('location', ChoiceType::class, ['mapped' => false])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => InstructionNotification::class
));
}
Action from controller:
/**
* #Route("/admin/api/instructions", name="admin_api_instructions")
* #param Request $request
* #return JsonResponse
*/
public function getInstructionsByCompanyId(Request $request)
{
$id = $request->get('id');
if (!$id) {
return new JsonResponse('No data', 404);
}
$instructions = $this->getDoctrine()->getRepository('OctaneBundle:Instruction')->findInstructionsByCompanyId($id);
return new JsonResponse($instructions);
}
findInstructionsByCompanyId($id):
public function findInstructionsByCompanyId($id)
{
$qb = $this->createQueryBuilder('i');
if ($id) {
$qb
->where('i.company = :id')
->setParameter('id', $id);
}
return $qb->getQuery()->getResult();
}
response from api (i.e.: admin/api/instructions?id=1):
[{"id":2,"label":"First instruction"},{"id":3,"label":"First instruction"}]
If you need any additional information please leave comments below. Thanks
Symfony's Validator expects that your submitted form will have a submitted instruction and location value that exists in the list you provided when creating your form in form type class. Since you are not providing any options for instructions and locations, you are getting a validation error.
In order to bypass this error you should use Symfony's Form Events in your buildForm function in your form type like this:
$builder->get('company')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) {
$company = $event->getForm()->getData();
$form = $event->getForm()->getParent();
$form->add('location', EntityType::class, array(
'class' => Location::class,
'query_builder' => function (EntityRepository $repo) use ($company) {
return $repo->createQueryBuilder('location')
->where('location.company = :company')
->setParameter('company', $company->getId());
}
));
$form->add('instruction', EntityType::class, array(
'class' => Instruction::class,
'query_builder' => function (EntityRepository $repo) use ($company) {
return $repo->createQueryBuilder('instruction')
->where('instruction.company = :company')
->setParameter('company', $company->getId());
}
));
}
);
Thanks for the answer but I found out more elegant solution for my case. So,
my formType now:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
...
->add('instruction', FormattedSelectType::class, ['class' => Instruction::class])
->add('location', FormattedSelectType::class, ['class' => Location::class])
;
}
FormattedSelectType:
class FormattedSelectType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'choice_label' => function (FormattedLabelInterface $entity) {
return $entity->getFormattedLabel();
}
));
}
/**
* {#inheritdoc}
*/
public function getParent()
{
return EntityType::class;
}
}
Etities Location and Instruction entities implement JsonSerializable and custom FormattedLabelInterface interface and have the next methods:
/**
* #return string
*/
public function getFormattedLabel()
{
return sprintf(self::LABEL_FORMATTED, $this->zip, $this->city, $this->name, $this->address, $this->phone);
}
/**
* #return array|mixed
*/
public function jsonSerialize()
{
return [
'id' => $this->id,
'label' => $this->getFormattedLabel()
];
}
I am trying to set parameter to query builder in Form type. I want to set impact variable to form field query builder. I get impact from form options
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title');
$parentPage = $options["parentPage"];
$impact = $options["impact"];
if($parentPage != null){
$builder->add('parent', 'entity', array(
'class' => "CoreBundle:Page",
'choices' => array($parentPage)
));
}else{
$builder->add('parent', 'entity', array(
'class' => "CoreBundle:Page",
'query_builder' => function(PageRepository $pr){
$qb = $pr->createQueryBuilder('p');
$qb->where("p.fullPath NOT LIKE '/deleted%'");
$qb->andWhere('p.impact = :impact')
->setParameter('impact', $impact); <-'Undefined variable $impact'
return $qb;
},
));
}
Why this code is shown to be wrong, it says that $impact is undefined variable. Isn't it global variable that can be accessed from anywhere in the buildForm function?
The problem is that you need to explicitly specify variables passed to a closure (aka the query_builder function):
$builder->add('parent', 'entity', array(
'class' => "CoreBundle:Page",
'query_builder' => function(PageRepository $pr) use ($impact) { // ADD
$qb = $pr->createQueryBuilder('p');
$qb->where("p.fullPath NOT LIKE '/deleted%'");
$qb->andWhere('p.impact = :impact')
->setParameter('impact', $impact); <-'Undefined variable $impact'
return $qb;
},
));
Most languages don't need this but php does.
See example 3 : http://php.net/manual/en/functions.anonymous.php
It sounds like you arent't passing the parameters into your form builder.
If you dump($options) in your buildForm function do you see them passed?
To add a custom value into the options your form type should be like;
<?php
// src/AppBundle/Form/Enitiy/PageType.php
namespace AppBundle\Form\Entity;
use Symfony\Component\Form\AbstractType,
Symfony\Component\Form\FormBuilderInterface,
Symfony\Component\OptionsResolver\OptionsResolver;
class PageType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title');
// ...
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Page',
'parentPage' => false,
'impact' => false
));
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_page;
}
}
Then your controller action would be something like;
$form = $this->createForm(new PageType(), $page, [
'parentPage' => 'foo',
'impact' => 'bar'
]);
I have a problem with collection form type. My entities:
User
use Doctrine\Common\Collections\ArrayCollection;
/**
* #OneToMany(targetEntity="Comment", mappedBy="user")
*/
protected $comments;
public function __construct() {
$this->comments= new ArrayCollection();
}
Comment
/**
* #ManyToOne(targetEntity="User", inversedBy="comments")
* #JoinColumn(name="user_id", referencedColumnName="id")
**/
protected $user;
Formbuilder:
$form = $silex['form.factory']->createBuilder('form', $user)
->add('comments', 'collection', array(
'type' => 'text',
'options' => array(
'required' => false,
'data_class' => 'Site\Entity\Comment'
),
))
->getForm();
and is returned error:
Catchable fatal error: Object of class Site\Entity\Comment could not be converted to string in C:\XXX\vendor\twig\twig\lib\Twig\Environment.php(331) : eval()'d code on line 307 Call Stack
I think that you might struggle to use a text type collection field here, since you want a field which is a collection of complex entities and not just one which is an array of strings.
I would suggest adding a new Form type for the Comment Entity:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class CommentType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('text', 'text', array());
}
public function getName() {
return 'comment';
}
public function getDefaultOptions(array $options) {
return array(
'data_class' => 'Site\Entity\Comment'
);
}
}
Then in the original Formbuilder, reference this type:
$form = $silex['form.factory']->createBuilder('form', $user)
->add('comments', 'collection', array(
'type' => new CommentType(),
'options' => array(
'required' => false,
'data_class' => 'Site\Entity\Comment'
'allow_add' => true,
'allow_delete' => true
),
))
->getForm();
I have form type for build form of category:
class CategoryType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('published', 'checkbox', array(
'required' => FALSE,
))
->add('parent', 'entity', array(
'class' => 'BWBlogBundle:Category',
'property' => 'name',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('c')
->where('c.id != :id')
->setParameter('id', ... /* I need to get category ID here */)
;
},
'required' => FALSE,
'empty_value' => 'Корневая категория',
))
// other my code
How can I get category ID of entity in query_builder closure in buildForm action?
answer for your question into this two questions symfony-2-how-to-pass-data-to-formbuilder and passing-data-from-controller-to-type-symfony2
1) create category variable and __construct() method into CategoryType class:
private category;
public function __construct(yourBundle\Category $category){
$this->category = $category ;
}
2) use category variable in buildForm() method into CategoryType class :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$category = $this->category;
$builder
->add('published', 'checkbox', array(
'required' => FALSE,
))
->add('parent', 'entity', array(
'class' => 'BWBlogBundle:Category',
'property' => 'name',
'query_builder' => function(EntityRepository $er) use ($category){
return $er->createQueryBuilder('c')
->where('c.id != :id')
->setParameter('id', $category->getId())
;
},
'required' => FALSE,
'empty_value' => 'Корневая категория',
))
}
finally when you create form in your controller :
$category = new Category();
$form = $this->createForm(new CategoryType($category),$category);
I don't know if these answers are as valid now. If you have the following code to create a form in your controller:
$fooEntity = $entityManager->find(FooEntity::class, 123);
$form = $this->createForm(MyFormType::class, $fooEntity);
... then the MyFormType::buildForm() method will be passed the $options parameter which will have $options['data'] that contains the entity that you passed to createForm(), i.e., $fooEntity in this case. This is assuming you don't override the 'data' key option with your own value. So you should be able to get the entity's ID from that.
class CategoryType extends AbstractType
{
private $category_id;
public function __construct($category_id=null)
{
$this->category_id = $category_id;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('published', 'checkbox', array(
'required' => FALSE,
))
->add('parent', 'entity', array(
'class' => 'BWBlogBundle:Category',
'property' => 'name',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('c')
->where('c.id != :id')
->setParameter('id', $this->category_id) /* I need to get category ID here */)
;
},
'required' => FALSE,
'empty_value' => 'Корневая категория',
))
// other my code
}
And when you create your form, do something like
public myFooController()
{
//retrieve %category_id here
$form = $this->creteForm(new CategoryType($category_id));
[...]
}
$options['data'] contains the entity for which the type is built.
I'm using entity choice list in my form. I want to use only specific entities (in example: only groups that user belongs to)
So, in controller, I'm getting these groups, and trying to pass them into formBuider.
Controller:
/.../
$groups = $em->getRepository('VendorMyBundle:Group')->getUserGroups($user);
$form = $this->createForm(new Message($groups), $message);
/.../
so, what now? how to use it in formBuilder?
how to change this line to use passed array of groups?
->add('group','entity',array('class' => 'Vendor\MyBundle\Entity\Group', 'label'=>'Group:'))
or in the other way:
class MessageType
{
/.../
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('group','entity',
array(
'class' => 'Vendor\MyBundle\Entity\Group',
'property' => 'name',
'query_builder' => function ($repository) {
$qb = $repository->createQueryBuilder('group');
$qb->add('where', 'group.administrator = :user');
$qb->setParameter('user', $user->getId());
return $qb;
},
'label' => 'Group'
)
)
// Continue adding fields
;
}
/.../
}
so how can i get object $user to use in form builder? ($user represent current logged user)
You can give the object you want to use in the __construct() method.
Eg :
$form = $this
->get('form.factory')
->create(new ApplyStepOneFormType($this->company, $this->ad), $applicant);
In your form type :
function __construct(\Your\Bundle\Entity\Company $company, \DYB\ConnectBundle\Entity\Ad $ad) {
$this->company = $company;
$this->ad = $ad;
}
And then in your form type in buildForm method :
$company = $this->company;
$builder->add('ad', 'entity', array(
'class' => '\Your\Bundle\Entity\Ad',
'query_builder' => function(\Your\Bundle\Repository\AdRepository $er) use ($company) {
return $er->getActiveAdsQueryBuilder($company);
},
));
//In controller pass the value which you want to use in builder form in array like
$object = new Question();
$form->create(new QuestionType() , $object , array('sqtname'=>2,'question_type'=>2));
//In Form type class
public function buildForm(FormBuilderInterface $builder , array $options)
{
//for setting data field dynamically
if (array_key_exists('question_type', $options) && $options['question_type'] != '') {
$data = $em->getReference("RecrutOnlineStandardBundle:StdQuestionType",$options['question_type']->getId());
} else {
$data = "";
}
$builder->add('StdQuestionType', 'entity', array(
'class' => 'TestStandardBundle:StdQuestionType',
'property' => 'name',
'empty_value' => 'Sélectionner un question type',
'required' => true,
'data' => $data,
'query_builder' => function(EntityRepository $er ) use ( $options ) {
if (isset($options['sqtname']) && $options['sqtname'] != '') {
return $er->createQueryBuilder('sqt')
->where("sqt.name!= ".$options['sqtname']);
} else{
return $er->createQueryBuilder('sqt');
}
}
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Test\QuestionBundle\Entity\Question',
'required' => false,
'sqtname' => '',
'question_type' =>''
));
}
Bacteries' solution IS NOT a good one. For example, if you declare your type as service, it is impossible to pass an object to constructor.
A perfect solution is options - just pass data as option to form builder.
If you want to use custom query, you have to set query_builder option as follows:
use Doctrine\ORM\EntityRepository;
...
$message = new Message();
$form = $this->createFormBuilder($message)
->add('group', 'entity', array(
'class' => 'Vendor\MyBundle\Entity\Group',
'label'=>'Group:',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('g')
->... // whatever you want to do
}
))
->getForm();
You can find more info about query builder in Doctrine manual and about options for entity in Symfony2 manual.
Bacteries' solution is a real good one. Just a note to save headache to other guy like me :)
In this part may I point out the use ($company) part.
It was hidden by the frame and of course nothing works properly without it.
$builder->add('ad', 'entity', array(
'class' =>
'\Your\Bundle\Entity\Ad',
'query_builder' =>
function(\Your\Bundle\Repository\AdRepository $er) use ($company) {
return $er->getActiveAdsQueryBuilder($company);
},
)
);
Best way (my opinion) is give to your form entityManager and select all you need in it. But don't forget to declare empty key in setDefaults() otherwise data won't pass to your builder.
Something like this one
public function buildForm(FormBuilderInterface $builder, array $options)
{
$options['em']->getRepository(''); // select all you need
$builder->add('title', 'text')
->add('content', 'textarea');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Main\BlogBundle\Entity\Post',
'validation_groups' => array('post'),
'required' => false,
'em' => null // this var is for your entityManager
));
}
Apply EM as simple option...