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.
Related
Using Symfony 4.4 with Easy Admin 3:
I've a OneToOne relationship
class Usuario
{
...
/**
* #ORM\OneToOne(targetEntity=Hora::class, inversedBy="usuario", cascade={"persist", "remove"})
*/
private $hora;
...
}
class Hora
{
...
/**
* #ORM\OneToOne(targetEntity=Usuario::class, mappedBy="hora", cascade={"persist", "remove"})
*/
private $usuario;
...
}
I've got a CRUD Controller for Usuario:
class UsuarioCrudController extends AbstractCrudController
{
public function configureFields(string $pageName): iterable
{
...
return [
...
AssociationField::new('hora', 'Hora'),
];
Everything seems ok, but in the admin form for "Usuario", the field "hora" shows all values in database, even the ones already assigned to other "Usuario" entities:
I would like the dropdown control to show only not assigned values, PLUS the value of the actual "Usuario" entity, so the control be easy to use.
Which is the proper way to do this with easyadmin?
I've managed to code the field to show only the not associated "Hora" values, using $this->getDoctrine() and ->setFormTypeOptions([ "choices" => in UsuarioCrudController class,
but I am not able to access the actual entity being managed, nor in UsuarioCrudController class (maybe there it is not accesible) neither in Usuario class (I've tried here __construct(EntityManagerInterface $entityManager) to no avail as the value doesn't seem to be injected, dunno why).
It is possible to customize a few things in easy admin by either overriding EasyAdmin methods or listening to EasyAdmin events.
Example of methods:
public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder
public function createEntity(string $entityFqcn)
public function createEditForm(EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context): FormInterface
//etc..
Example of events:
use EasyCorp\Bundle\EasyAdminBundle\Event\AfterCrudActionEvent;
use EasyCorp\Bundle\EasyAdminBundle\Event\AfterEntityDeletedEvent;
use EasyCorp\Bundle\EasyAdminBundle\Event\AfterEntityPersistedEvent;
use EasyCorp\Bundle\EasyAdminBundle\Event\AfterEntityUpdatedEvent;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeCrudActionEvent;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityDeletedEvent;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityPersistedEvent;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityUpdatedEvent;
You could override easy admin createEditFormBuilder or createNewFormBuilder method, this way you could access the current form data and modify your hora field.
Something like :
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
use EasyCorp\Bundle\EasyAdminBundle\Config\KeyValueStore;
public function createEditFormBuilder(EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context): FormBuilderInterface {
$formBuilder = parent::createEditFormBuilder($entityDto, $formOptions, $context);
$unassignedValues = $this->yourRepo->findUnassignedValues();
$data = $context->getEntity()->getInstance();
if(isset($data) && $data->getHora()){
//if your repo return an ArrayCollection
$unassignedValues = $unassignedValues->add($data->getHora());
}
// if 'class' => 'App\Entity\Hora' is not passed as option, an error is raised (see //github.com/EasyCorp/EasyAdminBundle/issues/3095):
// An error has occurred resolving the options of the form "Symfony\Bridge\Doctrine\Form\Type\EntityType": The required option "class" is missing.
$formBuilder->add('hora', EntityType::class, ['class' => 'App\Entity\Hora', 'choices' => $unassignedValues]);
return $formBuilder;
}
Currently, easyadmin3 still lack documentation so sometimes the best way to do something is to look at how easy admin is doing things.
fwiw, the actual entity being edited can be accessed in a Symfony easyadmin CrudController's configureFields() method using:
if ( $pageName === 'edit' ) {
...
$this->get(AdminContextProvider::class)->getContext()->getEntity()->getInstance()
...
This way in configureFields() I could add code to filter my entities:
$horas_libres = $this->getDoctrine()->getRepository(Hora::class)->findFree();
and then add the actual entity value also, which is what I was trying to do:
array_unshift( $horas_libres,
$this->get(AdminContextProvider::class)->getContext()->getEntity()->getInstance()->getHora() );
Now the field can be constructed in the returned array with "choices":
return [ ...
AssociationField::new('hora', 'Hora')->setFormTypeOptions([
"choices" => $horas_libres
]),
]
I have Entity like this
class User implements UserInterface
{
// ...
/**
* Many Departments have One Central Department (User)
* #ORM\ManyToOne(targetEntity="User")
*/
private $centralDepartment;
// ...
}
with self-referencing association. In related buildForm I use
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('centralDepartment');
// ...
}
}
and it creates in my view select list with list of Users. It's correct.
But the goal is to show on the list only Users with specific Role. If there is possibility I want also to validate before saving in database if selected User has specific Role.
Should I use option choice_loader from https://symfony.com/doc/3.4/reference/forms/types/choice.html or is there some better option in Symfony? I tried to change at first the label using
->add('centralDepartment', ChoiceType::class, array('label' => 'Choose Central Department'));
But my select list is empty now.
First try using EntityType instead of ChoiceType which is a more specialized ChoiceType for entity relations.
With EntityType you have a query_builder option to select the wanted choices.
See: https://symfony.com/doc/current/reference/forms/types/entity.html#query-builder
This could also be achieved through the choice_loader option with a ChoiceType but requires more work.
Going by #Joe suggestion I used EntityType
->add('centralDepartment', EntityType::class, array(
'class' => 'AppBundle:User',
'query_builder' => function(UserRepository $er) {
return $er->findAllBC(true);
}
));
where findAllBC(true) is my method in UserRepository to return Query Builder object which included specific Users for my use:
public function findAllBC($returnQB = false)
{
$qb = $this->createQueryBuilder('u')
->andWhere('u.roles LIKE :role')
->setParameter('role', '%BC%');
if(!$returnQB) return $qb->getQuery()->execute();
else return $qb;
}
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
I'm trying by overriding the controller and the formtype, to show the roles from the selected group in my view, but I don't find the right way. I've followed the steps to override everything, that works, but problem comes when I try to say to the service that I'm passing to the constructor an entity object.
As the formtype has to be overridden, how to pass through the service that you need to implement, my Group entity?
Does anyone have an idea of how to achieve that?
Here's what I've done:
Override the controller, and when creating the Form, pass the $group entity
$formFactory = $this->container->get('fos_user.group.form.factory');
$form = $formFactory->createForm($group); //Here
Override the form, and use a custom __construct method where I can pass my entity (maybe here is my error and that should be done in a better or other way)
public function __construct(Container $container, Groups $group)
{
$this->container = $container;
$this->roles = array_keys($this->container->getParameter('security.role_hierarchy.roles'));
$this->group = $group; #How?
}
The container to get the keys for the roles is passed without errors, that works.
Create the service as the documentation says (here comes the real problem and the exceptions)
x_s_cosmos.group.form.type:
class: X\S\CosmosBundle\Form\Type\GroupFormType
arguments: [#service_container, here_should_be_my_entity?]
tags:
- { name: form.type, alias: kosmos_group_form }
I'm really stacked with that and don't have any idea of how to go on.
Finally, after overriding the GroupController.php and adding a choice field type to my form, I could achieve my goal.
$form->add('roles', 'choice', array(
'choices' => $this->getExistingRoles(),
'data' => $group->getRoles(),
'label' => 'Roles',
'expanded' => true,
'multiple' => true,
'mapped' => true,
));
Where getExistingRoles() is:
$roleHierarchy = $this->container->getParameter('security.role_hierarchy.roles');
$roles = array_keys($roleHierarchy);
foreach ($roles as $role) {
$theRoles[$role] = $role;
}
return $theRoles;
I was just going in the wrong way, it was not so difficult to get the roles of a group and show them in an admin interface so that you can choose one of your system roles and give it to the group. No need to override the FormType, just the controller to add your own fields to the form.
Hope it helps, as it has given lot of headache to me.
You should not pass an entity to the constructor. If you need to access entity inside the form, you have to add an event listener to the form builder, like this:
public function buildForm(FormBuilder $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$entity = $event->getData();
// This is your Group entity, now do something with it...
if ($entity instanceof Group) {
// $form->add ...
}
});
}
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.