Unknown column in createFormBuilder - forms

When I try to render a form, I'm getting an error that says "Unknown column u0_.id in on clause" refering to u.id. But id property and getter is defined for User entity. So, what is happening then?
$form = $this->createFormBuilder()
->add('banish', 'entity', array(
'label' => $this->trans("Usuarios a expulsar"),
'class' => 'Pro\UserBundle\Entity\User',
'query_builder' => function(EntityRepository $er) use ($page){
return $er->createQueryBuilder('User')
->select('u')
->from('Pro\UserBundle\Entity\User', 'u')
->innerjoin('Pro\MembershipBundle\Entity\Membership', 'm', 'WITH', 'u.id = m.user')
->where( 'm.community = :community')
->setParameter('community', $page['community']);
},
))
->getForm();

Lets have a look at the original function createQueryBuilder() body which belongs from Doctrine\ORM\EntityRepository ,Reference EntityRepository
/**
* Create a new QueryBuilder instance that is prepopulated for this entity name
*
* #param string $alias
* #return QueryBuilder $qb
*/
public function createQueryBuilder($alias)
{
return $this->_em->createQueryBuilder()
->select($alias)
->from($this->_entityName, $alias);
}
It first parameter is $alias and the parameter in select() , from() is the same $alias and then it calls the createQueryBuilder() method of Doctrine\ORM\EntityManager, now considering your case
$er->createQueryBuilder('User')
->select('u')
->from('Pro\UserBundle\Entity\User', 'u')
The alias you have selected in createQueryBuilder() is User which is different from the alias you gave in select('u') , from('u') i guess that would be problem ,also when you have defined class in your entity type field you can just directly use it without using again from() method like
$form = $this->createFormBuilder()
->add('banish', 'entity', array(
'label' => $this->trans("Usuarios a expulsar"),
'class' => 'Pro\UserBundle\Entity\User',
'query_builder' => function(EntityRepository $er) use ($page){
return $er->createQueryBuilder('u')
->select('u,m')
->innerjoin('Pro\MembershipBundle\Entity\Membership', 'm', 'WITH', 'u.id = m.user')
->where( 'm.community = :community')
->setParameter('community', $page['community']);
},
))
->getForm();
Also using WITH in join will join the table with multiple conditions like ON(a.col1=b.col1 AND a.col2=b.col2) so if you have any property that points to membership defined in your user entity you can just use
->innerjoin('u.mapped_property')
it will automatically joins your user and membership table like ON(u.id=m.user_id)

Related

Sonata Admin MongoDB DataGrid filter

Situation
We are running Symfony 2.8 and the latest version of Sonata Admin along with Mongo as a data store. Please consider the following object which has been simplified for the sake of this question; it does work.
class Entry
{
/* #ID */
protected $id;
/* #String */
protected $type;
/* #String */
protected $content;
}
With the above, there will be lots of entries and from the Admin itself, we would like to filter by type.
Here's an example of the dataset
Problem
We can't create a set of selectable filters in the dataGrid function which are UNIQUE for type.
Attempts
Note that where needed, the EntryRepository is included as a namespace at the start of the file
NUMBER 1
With the below, we get the type duplicated many times
->add('type', null, array(), 'document', array(
'expanded' => true,
'class' => 'Application:Entry',
'query_builder' => function(EntryRepository $dr) {
return $dr->createQueryBuilder();
}
))
NUMBER 2
With the below, we get a 500 error with only the message "string". I think this is because when using distinct, Mongo prepares a set of arrays instead of unexecuted QueryBuilder object?
->add('type', null, array(), 'document', array(
'expanded' => true,
'class' => 'Application:Entry',
'query_builder' => function(Entryepository $dr) {
return $dr->createQueryBuilder()
->distinct('type');
}
))
NUMBER 3
The attempt below is to use Map reduce to perform the equivalent of an SQL "GROUP BY" however, the same STRING error as above is provided.
->add('type', '', array(), 'document', array(
'expanded' => true,
'class' => 'Application:Entry',
'query_builder' => function(EntryRepository $dr) {
return $dr->createQueryBuilder()
->group(array(), array('type'))
->reduce('function (obj, prev) { prev.type; }');
}
))
CRUDE WORKAROUND...discouraged
The below is a demonstration using the filter (as listed in the Sonata documentation) and it DOES work...for one type at a time.
->add('type', 'doctrine_mongo_callback', array(
'callback' => function($queryBuilder, $alias, $field, $value) {
if (!$value || $value['value'] == false) {
return true;
}
$queryBuilder
->field('type')->equals('fds');
return true;
},
'field_type' => 'checkbox'
))
Taken this approach, I think I'd have to go ahead and query the whole dataset getting the distinct values for type and then loop around each constructing the filter. This would work but be horribly messy.
QUESTION
What is the "best practise" way of performing this without turning the code in to a ruddy mess? Putting the query in a repository will still go and create a similar effect?
Thanks for reading
I'm posting the answer for anyone else who may be facing the same issue. I realised I was approaching the situation from the wrong angle and instead of any of the above, did the following
/* Prepare the options to be used in the filter */
$tagRepo = $this->getConfigurationPool()->getContainer()->get('repository.tag');
$types = $tagRepo->findTypes();
$choices = array();
/* Create a simple choice compatible array */
foreach ($types as $type) { $choices[$type] = $type; }
/* In the actial filter itself */
$datagridMapper
->add('type', 'doctrine_mongo_choice', array(), 'choice',
array('choices' => $choices))

Symfony2 form setting, unsetting associations

I have Company and Number entity which are related
/**
* #var Comapany
*
* #ORM\ManyToOne(targetEntity="Company", inversedBy="numbers", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="company", referencedColumnName="id", nullable=true, onDelete="RESTRICT")
* #Assert\NotBlank(groups={"client"})
* #Assert\Valid()
*/
private $company;
/**
* #var Number[]
* #ORM\OneToMany(targetEntity="Number", mappedBy="company", fetch="EXTRA_LAZY", cascade={"persist", "remove"})
* #Assert\Count(min="1")
*/
private $numbers;
I have created a form for creating and updating Company entity. This form should allow to set Number entities to it as well as unset them. This is how it looks rendered
And this is how it looks in code:
$builder
->add('name', 'text', [
'required' => false
])
->add('numbers', 'entity', [
'class' => 'AppBundle:Number',
'property' => 'number',
'placeholder' => '',
'required' => false,
'multiple' => true,
'query_builder' => function (EntityRepository $er) use ($builder) {
if ($builder->getData() && $id = $builder->getData()->getId()) {
return $er->createQueryBuilder('n')
->where('n.company is NULL')
->orWhere('n.company = :id')
->setParameter('id', $id);
}
return $er->createQueryBuilder('n')
->where('n.company is NULL');
}
]);
The problem is when creating new Company record, the form assigns Number entities, but the Number entities have property "company" which doesn't get assigned and so no relation is made. I have worked around this with form events:
$builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) {
foreach ($event->getData()->getNumbers() as $number) {
$number->setCompany($event->getData());
}
});
Which works for creating record, however when updating I have another issue, since I remove Number associations I have no access to them and thus can't update them in database. I could again select all Number entities assigned to form, and then filter out which were assigned to company and which were not and then manually update them, but this feels dirty and I would like to work it out in a clean way.
Finally found solution, as is turns out it's quite well documented:
http://symfony.com/doc/current/cookbook/form/form_collections.html
The changes I Had to make was:
1.Add by_reference property to entity form field: More information on that with an example: http://symfony.com/doc/current/cookbook/form/form_collections.html#allowing-new-tags-with-the-prototype
Basically what I figured that without this options symfony2 form uses own means of adding associations, and with this option set it calls methods "addNumber" and "removeNumber" inside Entity in which I had to manually add inverse side "number" association which goes to 2nd change I had to make.
$builder
->add('name', 'text', [
'required' => false
])
->add('numbers', 'entity', [
'class' => 'AppBundle:Number',
'property' => 'number',
'placeholder' => '',
'required' => false,
'multiple' => true,
'by_reference' => false, //
'query_builder' => function (EntityRepository $er) use ($builder) {
if ($builder->getData() && $id = $builder->getData()->getId()) {
return $er->createQueryBuilder('n')
->where('n.company is NULL')
->orWhere('n.company = :id')
->setParameter('id', $id);
}
return $er->createQueryBuilder('n')
->where('n.company is NULL');
}
]);
2.I had explicitly set Inverse side association to owning side by calling method setComapany($this) from owning (Company Entity) side.
/**
* Add numbers
*
* #param \AppBundle\Entity\Number $numbers
* #return Company
*/
public function addNumber(\AppBundle\Entity\Number $numbers)
{
$numbers->setCompany($this); //!Important manually set association
$this->numbers[] = $numbers;
return $this;
}
These 2 changes are enough to make form automatically add associations. However with removing associations there's a little bit more.
3.Change I had to make to correctly unset associations was inside controller action itself: I had to save currently set associations inside new ArrayCollection variable, and after form validation manually go through each item in that collection checking if it exists after form was validated. Important note:
I had also manually unset inverse side association to owning side by calling:
"$number->setCompany(null);"
public function editAction(Request $request, Company $company)
{
$originalNumbers = new ArrayCollection();
foreach ($company->getNumbers() as $number) {
$originalNumbers->add($number);
}
$form = $this->createForm(new CompanyType(), $company);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
foreach ($originalNumbers as $number) {
if (false === $company->getNumbers()->contains($number)) {
$company->getNumbers()->removeElement($number);
$number->setCompany(null); //!Important manually unset association
}
}
$em->persist($company);
$em->flush();
return $this->redirectToRoute('companies');
}
return $this->render('AppBundle:Company:form.html.twig', [
'form' => $form->createView()
]);
}
All of these steps are required to make this kind of logic function properly, luckily I was able to really good documentation for that.
PS. Note that I call
$em->persist($company);
$em->flush();
without persisting each "Number" Entity iterated inside loop which you can be seen in given Symfony2 documentation example, and which in this case would look like this:
$company->getNumbers()->removeElement($number);
$number->setCompany(null);
$em->persist($number);
This is because I setup Cascading Relations options inside my Entity class
/**
* #var Number[]
* #ORM\OneToMany(targetEntity="Number", mappedBy="company", fetch="EXTRA_LAZY", cascade={"persist", "remove"})
* #Assert\Count(min="1")
*/
private $numbers;
My advise for anyone struggling with this is to read whole http://symfony.com/doc/current/cookbook/form/form_collections.html thoroughly especially sections marked by special signs ✎✚❗☀💡

Add Entity field type to form with event subscriber class

I am doing something very similar to this cookbook example http://symfony.com/doc/current/cookbook/form/dynamic_form_generation.html#adding-an-event-subscriber-to-a-form-class
The main difference is that my field type is an entity and not a text type.
So my field subscriber preSetData method looks like this:
public function preSetData(DataEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return;
}
if(!$data->getIsCategorized()){
$form->add(
$this->factory->createNamed('categories', 'entity', array(
'class' => 'My\PostBundle\Entity\Category',
'property' => 'name',
'multiple' => true,
'label' => 'Category'
)
)
);
}
}
This is giving the following error
Class does not exist
500 Internal Server Error - ReflectionException
If I add the entity directly in my form type with $builder->add('categories, 'entity', array(... it works fine
Is it possible to attach an entity field type to a form using a field event subscriber in this fashion?
I ran into the same problem, and actually it's because the factory->createNamed() method has more argument than the builder->add
The third argument isn't the options array, but a "data" argument.
So here's what you should do :
$form->add(
$this->factory->createNamed('categories', 'entity', null, array(
'class' => 'My\PostBundle\Entity\Category',
'property' => 'name',
'multiple' => true,
'label' => 'Category'
)
)
);
(add null before the options array)
Whether you attach a field in the type or by means of an event listener/subscriber should make no difference. Either you have a small mistake somewhere (likely), or that's a bug, in which case you should submit it to the issue tracker.

Symfony2 entity field type alternatives to "property" or "__toString()"?

Using Symfony2 entity field type one should specify property option:
$builder->add('customers', 'entity', array(
'multiple' => true,
'class' => 'AcmeHelloBundle:Customer',
'property' => 'first',
));
But sometimes this is not sufficient: think about two customers with the same name, so display the email (unique) would be mandatory.
Another possibility is to implement __toString() into the model:
class Customer
{
public $first, $last, $email;
public function __toString()
{
return sprintf('%s %s (%s)', $this->first, $this->last, $this->email);
}
}
The disadvances of the latter is that you are forced to display the entity the same way in all your forms.
Is there any other way to make this more flexible? I mean something like a callback function:
$builder->add('customers', 'entity', array(
'multiple' => true,
'class' => 'AcmeHelloBundle:Customer',
'property' => function($data) {
return sprintf('%s %s (%s)', $data->first, $data->last, $data->email);
},
));
I found this really helpful, and I wound a really easy way to do this with your code so here is the solution
$builder->add('customers', 'entity', array(
'multiple' => true,
'class' => 'AcmeHelloBundle:Customer',
'property' => 'label',
));
And in the class Customer (the Entity)
public function getLabel()
{
return $this->lastname .', '. $this->firstname .' ('. $this->email .')';
}
eh voila :D the property get its String from the Entity not the Database.
Passing a closure does not work yet, but will be added to Symfony soon: https://github.com/symfony/symfony/issues/4067
It seems this can be achievable by adding following block after elseif ($this->labelPath) block in ObjectChoiceList.php.
elseif (is_callable($this->labelPath)) {
$labels[$i] = call_user_func($this->labelPath, $choice);
}
Haven't tried it though :).

Symfony2 Setting a default choice field selection

I am creating a form in the following manner:
$form = $this->createFormBuilder($breed)
->add('species', 'entity', array(
'class' => 'BFPEduBundle:Item',
'property' => 'name',
'query_builder' => function(ItemRepository $er){
return $er->createQueryBuilder('i')
->where("i.type = 'species'")
->orderBy('i.name', 'ASC');
}))
->add('breed', 'text', array('required'=>true))
->add('size', 'textarea', array('required' => false))
->getForm()
How can I set a default value for the species listbox?
Thank you for your response, I apologise, I think I should rephrase my question. Once I have a value that I retrieve from the model, how do I set that value as SELECTED="yes" for the corresponding value in the species choice list?
So, that select option output from the TWIG view would appear like so:
<option value="174" selected="yes">Dog</option>
You can define the default value from the 'data' attribute. This is part of the Abstract "field" type (http://symfony.com/doc/2.0/reference/forms/types/field.html)
$form = $this->createFormBuilder()
->add('status', 'choice', array(
'choices' => array(
0 => 'Published',
1 => 'Draft'
),
'data' => 1
))
->getForm();
In this example, 'Draft' would be set as the default selected value.
If you use Cristian's solution, you'll need to inject the EntityManager into your FormType class. Here is a simplified example:
class EntityType extends AbstractType{
public function __construct($em) {
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options){
$builder
->add('MyEntity', 'entity', array(
'class' => 'AcmeDemoBundle:Entity',
'property' => 'name',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('e')
->orderBy('e.name', 'ASC');
},
'data' => $this->em->getReference("AcmeDemoBundle:Entity", 3)
));
}
}
And your controller:
// ...
$form = $this->createForm(new EntityType($this->getDoctrine()->getManager()), $entity);
// ...
From Doctrine Docs:
The method EntityManager#getReference($entityName, $identifier) lets you obtain a reference to an entity for which the identifier is known, without loading that entity from the database. This is useful, for example, as a performance enhancement, when you want to establish an association to an entity for which you have the identifier.
the solution: for type entity use option "data" but value is a object. ie:
$em = $this->getDoctrine()->getEntityManager();
->add('sucursal', 'entity', array
(
'class' => 'TestGeneralBundle:Sucursal',
'property'=>'descripcion',
'label' => 'Sucursal',
'required' => false,
'data'=>$em->getReference("TestGeneralBundle:Sucursal",3)
))
I think you should simply use $breed->setSpecies($species), for instance in my form I have:
$m = new Member();
$m->setBirthDate(new \DateTime);
$form = $this->createForm(new MemberType, $m);
and that sets my default selection to the current date. Should work the same way for external entities...
If you want to pass in an array of Doctrine entities, try something like this (Symfony 3.0+):
protected $entities;
protected $selectedEntities;
public function __construct($entities = null, $selectedEntities = null)
{
$this->entities = $entities;
$this->selectedEntities = $selectedEntities;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('entities', 'entity', [
'class' => 'MyBundle:MyEntity',
'choices' => $this->entities,
'property' => 'id',
'multiple' => true,
'expanded' => true,
'data' => $this->selectedEntities,
]);
}
I don't think you should use the data option, because this does more than just setting a default value.
You're also overriding any data that's being passed to the form during creation. So basically, you're breaking
support for that feature. - Which might not matter when you're letting the user create data, but does matter when you
want to (someday) use the form for updating data.
See http://symfony.com/doc/current/reference/forms/types/choice.html#data
I believe it would be better to pass any default data during form creation. In the controller.
For example, you can pass in a class and define the default value in your class itself.
(when using the default Symfony\Bundle\FrameworkBundle\Controller\Controller)
$form = $this->createForm(AnimalType::class, [
'species' => 174 // this id might be substituted by an entity
]);
Or when using objects:
$dog = new Dog();
$dog->setSpecies(174); // this id might be substituted by an entity
$form = $this->createForm(AnimalType::class, $dog);
Even better when using a factory:
(where dog probably extends from animal)
$form = $this->createForm(AnimalType::class, DogFactory::create());
This will enable you to separate form structure and content from each other and make
your form reusable in more situations.
Or, use the preferred_choices option, but this has the side effect of moving the default option to the top of your form.
See: http://symfony.com/doc/current/reference/forms/types/choice.html#preferred-choices
$builder->add(
'species',
'entity',
[
'class' => 'BFPEduBundle:Item',
'property' => 'name',
'query_builder' => ...,
'preferred_choices' => [174] // this id might be substituted by an entity
]
);
I'm not sure what you are doing wrong here, when I build a form using form classes Symfony takes care of selecting the correct option in the list. Here's an example of one of my forms that works.
In the controller for the edit action:
$entity = $em->getRepository('FooBarBundle:CampaignEntity')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find CampaignEntity entity.');
}
$editForm = $this->createForm(new CampaignEntityType(), $entity);
$deleteForm = $this->createDeleteForm($id);
return $this->render('FooBarBundle:CampaignEntity:edit.html.twig', array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
The campaign entity type class (src: Foo\BarBundle\Form\CampaignEntityType.php):
namespace Foo\BarBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Doctrine\ORM\EntityRepository;
class CampaignEntityType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('store', 'entity', array('class'=>'FooBarBundle:Store', 'property'=>'name', 'em'=>'my_non_default_em','required' => true, 'query_builder' => function(EntityRepository $er) {return $er->createQueryBuilder('s')->orderBy('s.name', 'ASC');}))
->add('reward');
}
public function getName()
{
return 'foo_barbundle_campaignentitytype';
}
}
From the docs:
public Form createNamed(string|FormTypeInterface $type, string $name, mixed $data = null, array $options = array())
mixed $data = null is the default options. So for example I have a field called status and I implemented it as so:
$default = array('Status' => 'pending');
$filter_form = $this->get('form.factory')->createNamedBuilder('filter', 'form', $default)
->add('Status', 'choice', array(
'choices' => array(
'' => 'Please Select...',
'rejected' => 'Rejected',
'incomplete' => 'Incomplete',
'pending' => 'Pending',
'approved' => 'Approved',
'validated' => 'Validated',
'processed' => 'Processed'
)
))->getForm();
Setting default choice for symfony2 radio button
$builder->add('range_options', 'choice', array(
'choices' => array('day'=>'Day', 'week'=>'Week', 'month'=>'Month'),
'data'=>'day', //set default value
'required'=>true,
'empty_data'=>null,
'multiple'=>false,
'expanded'=> true
))
The form should map the species->id value automatically to the selected entity select field. For example if your have a Breed entity that has a OnetoOne relationship with a Species entity in a join table called 'breed_species':
class Breed{
private $species;
/**
* #ORM\OneToOne(targetEntity="BreedSpecies", mappedBy="breed")
*/
private $breedSpecies;
public function getSpecies(){
return $breedSpecies->getSpecies();
}
private function getBreedSpecies(){
return $this->$breedSpecies;
}
}
The field 'species' in the form class should pick up the species->id value from the 'species' attribute object in the Breed class passed to the form.
Alternatively, you can explicitly set the value by explicitly passing the species entity into the form using SetData():
$breedForm = $this->createForm( new BreedForm(), $breed );
$species = $breed->getBreedSpecies()->getSpecies();
$breedForm->get('species')->setData( $species );
return $this->render( 'AcmeBundle:Computer:edit.html.twig'
, array( 'breed' => $breed
, 'breedForm' => $breedForm->createView()
)
);
You can either define the right default value into the model you want to edit with this form or you can specify an empty_data option so your code become:
$form = $this
->createFormBuilder($breed)
->add(
'species',
'entity',
array(
'class' => 'BFPEduBundle:Item',
'property' => 'name',
'empty_data' => 123,
'query_builder' => function(ItemRepository $er) {
return $er
->createQueryBuilder('i')
->where("i.type = 'species'")
->orderBy('i.name', 'ASC')
;
}
)
)
->add('breed', 'text', array('required'=>true))
->add('size', 'textarea', array('required' => false))
->getForm()
;
You can use "preferred_choices" and "push" the name you want to select to the top of the list. Then it will be selected by default.
'preferred_choices' => array(1), //1 is item number
entity Field Type