Form widget not bound to a Doctrine2 association? - symfony

I have a form for creating a new BroadcastMessage entity and i need to display a widget of type <select multiple="multiple"> bound to excludedUsers property, not directly related to a Doctrine2 an association.
Inside my BroadcastMessageType class (inherits from AbstractType):
$builder->add('excludedUsers, 'entity', array(
'class' => 'Acme\MyBundle\Enrity\User',
'property' => 'username',
'multiple' => true
));
This of course works for creating a new BroadcastMessage; but on editAction i need a complex query to get excluded users. I need to compute excluded users looking for a record in a cross-reference table named broadcast_message_reference.
My question is fairly simple: where to add this "complex query" in order to get excludedUsers property correctly bound to the <select multiple="multiple"> widget? Inside my getExludedUsers method? If yes, how i'm supposed to access the entity repository for that query?
class BroadcastMessage
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
private excludedUsers;
public function __costrunct()
{
$this->excludedUsers = new ArrayCollection();
}
public function addExcludedUser(Acme\MyBundle\Enrity\User $user)
{
$this->excludedUsers[] = $user;
return $this;
}
public function getExcludedUsers() { return $this->excludedUsers; }
}

Actually, I think, you need query_builder option within your form item definition:
$builder->add('excludedUsers', 'entity', array(
'class' => 'Acme\MyBundle\Enrity\User',
'property' => 'username',
'multiple' => true,
'query_builder' => function(EntityManager $em){
// you have an instance of EntityManager so you may build
// arbitrary QueryBuilder. Just remember to return it
// for example:
$qb = $em->createQueryBuilder()
->from('Acme\MyBundle\Enrity\User u')
->where('u.excluded = true');
return $qb;
},
'property' => 'username'
));
This is just rough example, but you can find more about entity form type here.

I think your problem can be solved by Form events. Create a service for EventSubscriber class, inject EntityManager and then subscribe for FormEvents::SET_DATA event.

Related

One to many relation throw exception on save

I have two entites. "Institution" and "User". The Insitution have a onetomany relation to user. I create a form "InstitutionType" and if i want to save a new insitution the "handleReqeust($request)" throw this exception.
Neither the property "users" nor one of the methods "addUser()"/"removeUser()", "setUsers()", "users()", "__set()" or "__call()" exist and have public access in class "App\UserBundle\Entity\Institution".
Entity User
/**
* #ORM\ManyToOne(targetEntity="Institution", inversedBy="users")
* #ORM\JoinColumn(name="institution_id", referencedColumnName="id")
*/
protected $institution;
public function setInstitution(\App\UserBundle\Entity\Institution $institution = null)
{
$this->institution = $institution;
return $this;
}
public function getInstitution()
{
return $this->institution;
}
Entity Institution
/**
* #ORM\OneToMany(targetEntity="App\UserBundle\Entity\User", mappedBy="institution")
*/
protected $users;
public function addUser(\App\UserBundle\Entity\User $users)
{
$this->users[] = $users;
return $this;
}
public function removeUser(\Ebm\UserBundle\Entity\User $users)
{
$this->users->removeElement($users);
}
public function getUsers()
{
return $this->users;
}
InstitutionType
$users = $this->entityManager->getRepository('AppUserBundle:User')->findByIsActive(true);
->add('users', 'entity', array(
'label' => 'responsibleperson',
'attr' => array(),
'class' => 'AppUserBundle:User',
'choices' => $users,
'multiple' => false,
'expanded' => false,
'empty_value' => '----------------')
)
Can someone help my to solve this issue?
Rename addUser to addUsers and removeUser to removeUsers.
Symfony2/Doctrine obviously has no knowledge of singular/plural words and can't guess that to add a single entry to the users collection it would be semantically more correct to call addUser() instead of addUsers.
Please note that you can always use the console command doctrine:generate:entities AcmeDemoBundle:Institution to generate missing fields in the entity classes.
If this doesn't help you need to make sure you use the same notation in your form type like in your entity configuration (annotation, yml or xml).

symfony filter collection field type like entity field type

I have a form with a collection field type.
I'd like to filter it as we can do for entity field types but I'm not finding the solution.
i found other similar questions but no satisfiable answer so far. Can we do something like :
$builder
->add('userIngredients', 'collection', array(
'type' => new UserImportedIngredientType($this->userIngredients),
'query_builder'=>$this->queryBuilder,
))
;
If not, can we use form listener event to exclude some elements based on the object property ? How ?
This collection represents userIngredients that I want the user to be able to change if they have their property isImported set to true, hence the search for the query_builder solution.
Well, I figured I could do something as simple as build a regular form not attached to a parent entity.
In case this might help someone :
class UserImportedIngredientType extends AbstractType
{
protected $userIngredients;
protected $userImportedIngredients;
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
foreach ($this->userImportedIngredients as $userImportedIngredient)
{
/**
* #var $userImportedIngredient UserIngredient
*/
$builder
->add($userImportedIngredient->getId(), 'genemu_jqueryselect2_entity', array(
'query_builder'=>$this->userIngredients,
'class' => 'AppBundle:FoodAnalytics\UserIngredient',
'multiple' => false,
'label' => $userImportedIngredient->getName(),
'required'=>false,
'mapped' => false,
'data' => $userImportedIngredient
))
;
}
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_foodanalytics_user_imported_ingredient';
}
public function __construct($userIngredients, $userImportedIngredients)
{
$this->userIngredients=$userIngredients;
$this->userImportedIngredients=$userImportedIngredients;
}
}
I solved it like this in my case with additionally Sonata on top of Symfony. What I did was feeding the 'data' parameter the specifically doctrine queried array of entities result. (In Repository: return $queryBuilder->getQuery()->getResult();)
/** #var MyEntityRepository $myEntityRepository */
$myEntityRepository = $this->getEntityManager()->getRepository(MyEntity::class);
/** #var MyEntity[] $myEntities */
$myEntities = $myEntityRepository->findBySomeCriteriaFilter(
$parameter, Constants::specificConstant
);
$formMapper->add(
// html name=
'myEntityProperty',
\Symfony\Component\Form\Extension\Core\Type\CollectionType::class,
[
// specific form for MyEntity
'entry_type' => new Form\MyEntity\MyEntityType(),
'allow_add' => true,
'label' => false,
'entry_options' => [
'label' => false
],
// the filtered array of entities from doctrine repository query above
'data' => $myEntities,
]
);
Use this instead of sonata_type_model_list, I guess.
Also if you want to filter sonata_type_model, use EntityType instead and use the 'query_builder' option with a Closure and returning the queryBuilder instead of the array of entities. This is all very inconsistent, best don't use symfony and sonata at all.
As far as I know Collection does not have the query_builder option
So you cannot go this way.
Is hard to decipher what you are trying to do with 4 lines of formType.
Your code looks alright, except the unsuppported query_builder, and you are already passing the userIngredients to the constructor.
My though is that if you need to filter this, then you shouldn't do it in the collection Type, but in other place.
EDITED:
On a second though, you are trying to filter the collection in the wrong place. Is not on the base collection , but in :
class UserImportedIngredientType extends AbstractType {
function __construct( $userIngredients ) {
// Do your stuff
}
function buildForm( FormBuilderInterface $builder, array $options) {
// Add here your entity with the query_filter option :)
}
}
Check the other entries in SO over Collections, there are many of them

Symfony2 Unidirectional ManyToMany Collection Form

I'm stuck :-)
Maybe I just have the wrong keywords in my research. But I don't get through. So I hope one of the crowd can help me!
I have a unidirectional ManyToMany Association. When I try to submit the form (and therefore persist), I get the error:
A new entity was found through the relationship 'TrainerInnenPool\AppBundle\Entity\Trainer#applicationFields' that was not configured to cascade persist operations for entity: TrainerInnenPool\AppBundle\Entity\ApplicationField#0000000022ef36c600000000087bcbc3.
When I do "cascade persist" a new Entity is created which actually already exists.
I have 2 Entities:
Trainer
ApplicationField
The Trainer has a unidirectional ManyToMany association to the ApplicationField:
class Trainer {
public function __construct()
{
$this->applicationFields = new ArrayCollection();
}
[...]
/**
* #ORM\ManyToMany(targetEntity="ApplicationField")
*/
protected $applicationFields;
The ApplicationField has a self-referencing OneToMany association:
class ApplicationField {
public function __construct() {
$this->children = new ArrayCollection();
}
[...]
/**
* #ORM\OneToMany(targetEntity="ApplicationField", mappedBy="parent")
*/
private $children;
/**
* #ORM\ManyToOne(targetEntity="ApplicationField", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/
private $parent;
I want to create a form where I can add a Trainer - ApplicationField association.
Therefore I have an ApplicationFieldCollectionType:
class ApplicationFieldCollectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('applicationFields', 'collection', array(
'type' => new ApplicationFieldType(),
'allow_add' => true,
'label' => false
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'TrainerInnenPool\AppBundle\Entity\Trainer',
));
}
The embeded Type is as follows:
class ApplicationFieldType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('applicationFieldName', 'entity', array(
'class' => 'TrainerInnenPoolAppBundle:ApplicationField',
'label' => false,
'mapped' => false,
'property' => 'name',
'query_builder' => function(EntityRepository $repository) {
return $repository->createQueryBuilder('application_field')
->where('application_field.parent is NULL');
}
));
$builder->add('name', 'entity', array(
'class' => 'TrainerInnenPoolAppBundle:ApplicationField',
'label' => false,
'property' => 'name',
'query_builder' => function(EntityRepository $repository) {
return $repository->createQueryBuilder('application_field')
->where('application_field.parent = 1');
}
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'TrainerInnenPool\AppBundle\Entity\ApplicationField',
));
}
Last missing part: The controller:
public function editApplicationField($id, Request $request)
{
$entityManager = $this->getDoctrine()->getEntityManager();
$trainer = $entityManager->getRepository('TrainerInnenPoolAppBundle:Trainer')->find($id);
$editForm = $this->createForm(new ApplicationFieldCollectionType(), $trainer);
if ($request->getMethod() == 'POST') {
$editForm->handleRequest($request);
$entityManager->flush();
}
When I fetch the ApplicationField entities from the Trainer and try to persist those,
$editForm->handleRequest($request);
$af = $trainer->getApplicationFields();
foreach ($af as $applicationField) {
$trainer->addApplicationField($applicationField);
$entityManager->persist($applicationField);
}
$entityManager->flush();
I'm not able to do so, since I get a "Duplicate Entry For Key PRIMARY" - Exception.
I think I terribly miss any obvious point. If someone could help me, gave me a hint or just mentioned to update my question with information, I would be so thankful.
Kind regards...
Your nested type setters are not called because of a missing property:
->add('applicationFields', 'collection', array(
'type' => new ApplicationFieldType(),
...
'by_reference' => false
...
http://symfony.com/doc/current/reference/forms/types/collection.html#by-reference
When you plan to have collection fields that are addable/deletable("allow_add", "allow_delete"), you should always provide the "by_reference" => false option, in order to call the setters directly on the related entities and then build the association, rather than chain methods from the original entity.
Hope this helps!
Well you need the cascade={"persist"} annotation because you want to persist the whole entity with its association to ApplicationField. If you don't use cascade={"persist"}, you'd have to manually persist the entities.
The entities are already added to the trainer, so if you want to manually persist the entities you should remove the line
$trainer->addApplicationField($applicationField);
and only execute the persist.
This should work. Try it. But I think the effect will be the same as if you use cascade persist. So it's not the final solution I think, but the first step to understand the problem, why the manual persist didn't work before.

Symfony formbuilder self-reference entity grouped

I want a choice list (dropdown) grouped by parent categories (not selectable).
Is this even possible?
Example:
- Vehiculs (not selectable)
-- Car (selectable)
-- Boat (selectable)
- Computers (not selectable)
Entity Category
/**
* #ORM\OneToMany(targetEntity="Category", mappedBy="parent")
**/
private $children;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true, onDelete="SET NULL")
**/
private $parent;
Form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', 'text',
[
'attr' => ['placeholder' => 'Titel', 'class' => 'form-control', 'autocomplete' => 'off'],
'label' => false
]
)
...
->add('category', 'entity',
[
'property' => 'name',
'class' => '***ArticleBundle:Category',
]
)
;
}
With the code above i only get the parents and they are selectable.
I would like to group the children of those parents (1 depth) and make only the children selectable options.
Just posting this because this seems to be the first google hit for this problem and I had to solve it.
You in fact can directly tell symfony to create a tree select in FormBuilder with a small workaround which is totally clean.
The fact that the "Entity" is a child of "Choice" helps there a lot. The solution consists of three parts: Controller, FormType and Entity Repository
Let's start with the Controller
$em = $this->getDoctrine()->getManager();
$form = $this->createForm(new AcmeType(array(
'repository' => $em->getRepository('AcmeBundle:Category'),
)), $data, array(
'action' => *add your route here*
'method' => 'POST'
));
Straight forward form generation, except the adition of the repository as a parameter for your FormType (which we need there)
Your FormType includes the following
/** #var CategoryRepository */
protected $repository;
public function __construct($options = array()){
$this->repository = $options['repository'];
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
*add your other stuff here*
->add('category', 'entity', array(
'empty_value' => 'Please Select...',
'required' => true,
'choices' => $this->repository->getCategoryData(),
'class' => 'Acme\AcmeBundle\Entity\Category'
))
;
}
We create an Entity field type and add the "choices" and fill it with the response from the "getCategoryData".
In your Repository of your Data Entity (in this case Category Entity) you create the following function
public function getCategoryOptions()
{
$data = array();
$categories = $this
->createQueryBuilder('c')
->select('c')
->getQuery()
->getResult();
foreach( $categories as $category ){
/** #var Category $category */
if( !$category->getParent() ){
continue;
}
if(!array_key_exists($category->getParent()->getName(), $data) ){
$data[$category->getParent()->getName()] = array();
}
$data[$category->getParent()->getName()][$category->getId()] = $category;
}
return $data;
}
This simple function does a simple query to the Database to select your Categories and then runs a foreach through them and builds an array. As seen in the FormType above it parses that array directly to the 'choices' parameter of your Entity field type.
The Form Renderer of symfony is smart enough to add tags with labels.
Et voila, you have a grouped dropdown box with non-selectable "Parents".
You can not directly tell symfony to create a tree select in form builder.
First, you have to check here;
http://symfony.com/doc/current/reference/forms/types/entity.html
You can use query builder on your entity field to get parent - child relation but parents also would be selectable.
So you have to look another solutions like this:
How to disable specific item in form choice type?

Access currently logged in user in EntityRepository

I want to create a simple blog example where users have a favourite category attached to there account. This means the can only write articles for this category. (Some of them - mostly admins -will get the opportunity to switch categories, but that's not the problem... for now ^^)
So I first created a relation between the User- and the Category entity. Everything works fine. Each user now has a main category selected.
Only thing which bothers me, is that I cant get hold of the current logged in user in the EntityType (formbuilder) and EntityRepository classes.
In my "New Post" form there are relations to other entities (e.g. Tags). I use the 'entity' formtype in the EntityType class to generate these form elements. Now i wan't to filter the tags, to only allow tags which have the same category relation as the currently logged in users category to be selectable.
I tried to use the query_builder option from the entity formtype. But as i can't get the current user object, I don't know which category he has selected. Same problem with the EntityRepository.
Now I could filter the tags already in the PostController but the problem is, that I will need this over and over again. And therefore I don't wan't to code this everytime I add something new.
I thought it would be the best to place this filter in the EntityRepository. So I can always access the findAllByCategory. But I need the user-object in there.
What is the best way to accomplish this? Have searched a lot, but either I searched for the wrong terms or no one has this problem :)
You can get user object from Security Context
$user = $serviceContainer->get('security.context')->getToken()->getUser();
Small tip: In case if user is not logged in - you'll have string in $user, otherwise - User object.
Cheers ;)
You can inject security context in your form type defined as a service. Then in your tags field use the query builder with $user (current logged user) to filter tags which have the same category relation as the currently logged:
/** #DI\Service("form.type.post") */
class PostType extends \Symfony\Component\Form\AbstractType
{
/**
* #var \Symfony\Component\Security\Core\SecurityContext
*/
protected $securityContext;
/**
* #DI\InjectParams({"securityContext" = #DI\Inject("security.context")})
*
* #param \Symfony\Component\Security\Core\SecurityContext $context
*/
public function __construct(SecurityContext $securityContext)
{
$this->securityContext = $securityContext;
}
public function buildForm(FormBuilder $builder, array $options)
{
$user = $this->securityContext()->getToken()->getUser();
$builder->add('tags', 'entity', array(
'label' => 'Tags',
'class' => 'Acme\HelloBundle\Entity\Tag',
'property' => 'name',
'query_builder' => function(EntityRepository $er) use($user) {
return $er->getAllSuitableForUserChoiceQueryBuilder($user);
},
'multiple' => true,
'expanded' => true,
}
}
Filter tags into your repository:
class TagRepository extends EntityRepository
{
public function getAllSuitableForUserChoiceQueryBuilder(User $user)
{
// Filter tags that can be selected by a given user
}
}
tried to use the query_builder option from the entity formtype. But as i can't get the current user object, i don't know which category he has selected. Same problem with the EntityRepository.
As long the Category has a relation to the user, you should be able to get the User there.
For Example:
Controller
$someThing = new SomeThing();
$someThing->setUser($user);
$form = $this->createForm(new someThingType(), $someThing);
Form
$someThing = $options['data'];
$user = $someThing->getUser();
$builder->add('category', null, array(
'class' => 'MyBundle:Cateogry',
'query_builder' =>
function(EntityRepository $er) use ($user) {
return $er->getCategoriesForUser($user);
}
));
Repository
public function getCategoriesForUser($user)
{
$qb = $this->createQueryBuilder('c');
$qb->leftJoin('c.user', 'u', 'with', 'u.user = :user');
$qb->setParameter('user', $user)
;
return $qb;
}
this is not exactly your use-case but pretty similar to it. maybe it helps you.

Resources