54

I am using the FOS bundle and I want to retrieve all users with a given ROLE from the database.

What is the best way to do this?

Darryl Hein
  • 142,451
  • 95
  • 218
  • 261
Visavì
  • 2,333
  • 1
  • 21
  • 29
  • 1
    In the database the role field is a serialised array like: `a:2:{i:0;s:10:"ROLE_ADMIN";i:1;s:9:"ROLE_USER";}` – Visavì Jan 26 '12 at 10:51
  • Methods and the Serialization are currently discussed in this issue: https://github.com/FriendsOfSymfony/FOSUserBundle/issues/1308 – webDEVILopers Sep 26 '14 at 09:25

8 Answers8

111

Just add this in your UserRepository or replace $this->_entityName by YourUserBundle:User:

/**
 * @param string $role
 *
 * @return array
 */
public function findByRole($role)
{
    $qb = $this->_em->createQueryBuilder();
    $qb->select('u')
        ->from($this->_entityName, 'u')
        ->where('u.roles LIKE :roles')
        ->setParameter('roles', '%"'.$role.'"%');

    return $qb->getQuery()->getResult();
}

If you are using FOSUser Groups you should use:

/**
 * @param string $role
 *
 * @return array
 */
public function findByRole($role)
{
    $qb = $this->_em->createQueryBuilder();
    $qb->select('u')
        ->from($this->_entityName, 'u')
        ->leftJoin('u.groups', 'g')
        ->where($qb->expr()->orX(
            $qb->expr()->like('u.roles', ':roles'),
            $qb->expr()->like('g.roles', ':roles')
        ))
        ->setParameter('roles', '%"'.$role.'"%');

    return $qb->getQuery()->getResult();
}
Léo Benoist
  • 2,511
  • 1
  • 20
  • 18
  • 16
    This solution would risk fetching the wring users, if for example some users has the role ROLE_ADMIN and some users has the role ROLE_SUPER_ADMIN and you run $repository->findByRole('ADMIN') the above would match both users that has ROLE_ADMIN and ROLE_SUPER_ADMIN. Besides from this, you will not get the users that has an implicit role due to role hierachies (http://symfony.com/doc/current/book/security.html#hierarchical-roles). – tirithen Nov 01 '13 at 13:19
  • 6
    I just made a correction to fix what you are worry about role confusion. Besides from this it was asking for a >specefic< role and not a hierarchies. – Léo Benoist Nov 18 '13 at 15:27
  • 2
    If you do not have to check against role hierarchy, this answer is ok. If you have to check the child roles too, this answer is useless. – tomazahlin Sep 22 '14 at 13:39
  • 1
    This solution can be expensive performance-wise if you have a lot of users. Example : # Query_time: 5.254861 Lock_time: 0.000191 Rows_sent: 565 Rows_examined: 196442 SELECT * FROM user u WHERE u.roles LIKE '%ROLE_MANAGER%' OR u.roles LIKE '%ROLE_ADMIN%' OR u.roles LIKE '%ROLE_SUPER_ADMIN%'; – Ka. Jul 22 '16 at 10:43
  • I had to change the quotes on the setParameter to make this work: `->setParameter('roles', "%" . $role . "%")` – ilbonte Dec 03 '20 at 09:15
  • @ilbonte if you do that you risk having ROLE_X matching ROLE_X_Y – Léo Benoist Dec 04 '20 at 14:25
23

Well, if there is no better solution, I think I will go to a DQL query:

$query = $this->getDoctrine()->getEntityManager()
            ->createQuery(
                'SELECT u FROM MyBundle:User u WHERE u.roles LIKE :role'
            )->setParameter('role', '%"ROLE_MY_ADMIN"%');

$users = $query->getResult();
Visavì
  • 2,333
  • 1
  • 21
  • 29
10

As @Tirithen states, the problem is that you will not get the users that have an implicit role due to role hierarchy. But there is a way to work around that!

The Symfony security component provides a service that gives us all child roles for a specific parent roles. We can create a service that does almost the same thing, only it gives us all parent roles for a given child role.

Create a new service:

namespace Foo\BarBundle\Role;

use Symfony\Component\Security\Core\Role\RoleHierarchy;
use Symfony\Component\Security\Core\Role\Role;

/**
 * ReversedRoleHierarchy defines a reversed role hierarchy.
 */
class ReversedRoleHierarchy extends RoleHierarchy
{
    /**
     * Constructor.
     *
     * @param array $hierarchy An array defining the hierarchy
     */
    public function __construct(array $hierarchy)
    {
        // Reverse the role hierarchy.
        $reversed = [];
        foreach ($hierarchy as $main => $roles) {
            foreach ($roles as $role) {
                $reversed[$role][] = $main;
            }
        }

        // Use the original algorithm to build the role map.
        parent::__construct($reversed);
    }

    /**
     * Helper function to get an array of strings
     *
     * @param array $roleNames An array of string role names
     *
     * @return array An array of string role names
     */
    public function getParentRoles(array $roleNames)
    {
        $roles = [];
        foreach ($roleNames as $roleName) {
            $roles[] = new Role($roleName);
        }

        $results = [];
        foreach ($this->getReachableRoles($roles) as $parent) {
            $results[] = $parent->getRole();
        }

        return $results;
    }
}

Define your service for instance in yaml and inject the role hierarchy into it:

# Provide a service that gives you all parent roles for a given role.
foo.bar.reversed_role_hierarchy:
    class: Foo\BarBundle\Role\ReversedRoleHierarchy
    arguments: ["%security.role_hierarchy.roles%"]

Now you are ready to use the class in your own service. By calling $injectedService->getParentRoles(['ROLE_YOUR_ROLE']); you will get an array containing all parent roles that will lead to the 'ROLE_YOUR_ROLE' permission. Query for users that have one or more of those roles... profit!

For instance, when you use MongoDB you can add a method to your user document repository:

/**
 * Find all users with a specific role.
 */
public function fetchByRoles($roles = [])
{
    return $this->createQueryBuilder('u')
        ->field('roles')->in($roles)
        ->sort('email', 'asc');
}

I'm not into Doctrine ORM but I'm sure it won't be so different.

Sander Toonen
  • 3,463
  • 35
  • 54
  • This is a neat answer, but how do you use the new service with the `UserRepository`? Do you convert the repository into a service? – PeterB May 10 '16 at 07:56
  • 1
    @PeterB in a controller by example : `$roles = $this->get('foo.bar.reversed_role_hierarchy')->getParentRoles('ROLE_ADMIN'); $users = $userRepo->fetchByRoles($roles) ` – jona303 Jun 20 '16 at 12:49
  • 1
    By the way for Doctrine ORM the only way i found for the moment is to use Regexp via this bundle https://github.com/beberlei/DoctrineExtensions and in the repository `$qb->where("REGEXP(u.roles, '".implode('|',$roles)."') = 1");` – jona303 Jun 20 '16 at 12:55
10

If you have this requirement and your user list will be extensive, you will have problems with performance. I think you should not store the roles in a field as a serialized array. You should create an entity roles and many to many relationship with the users table.

smoreno
  • 3,129
  • 3
  • 32
  • 49
0

You can use just this on your DQL:

SELECT u FROM YourFavouriteBundle:User u WHERE u.roles [NOT] LIKE '%ROLE_YOUR_ROLE%'

Of course with QueryBuilder it's more elegant:

// $role = 'ROLE_YOUR_ROLE';
$qb->where('u.roles [NOT] LIKE :role')
   ->setParameter('role', "%$role%");
David Vartanian
  • 460
  • 4
  • 12
0

Finally i solved it, following is an exact solution:

public function searchUsers($formData)
{
    $em = $this->getEntityManager();
    $usersRepository = $em->getRepository('ModelBundle:User');
    $qb = $usersRepository->createQueryBuilder('r');

    foreach ($formData as $field => $value) {
        if($field == "roles"){
            $qb->andWhere(":value_$field MEMBER OF r.roles")->setParameter("value_$field", $value);
        }else{
            $qb->andWhere("r.$field = :value_$field")->setParameter("value_$field", $value);
        }
    }
    return $qb->getQuery()->getResult();
}

Cheers!

Muzafar Ali
  • 1,362
  • 1
  • 12
  • 18
0

In case you need to filter users by role using a DQL filter in a YAML file (In EasyAdminBundle for instance)

entities:
    Admin:
        class: App\Entity\User
        list:
            dql_filter: "entity.roles LIKE '%%ROLE_ADMIN%%'"
Kaizoku Gambare
  • 3,143
  • 3
  • 29
  • 41
0

Here I give an alternative solution :
I find users of roles for a given array
In controller I call the function like that

$users = $userRepository->findUsersOfRoles(['ROLE_ADMIN', 'ROLE_SUPER_USER']);

Then in my repository I make a loop to generate condition and set the parameters :

public function findUsersOfRoles($roles)
{
    $condition = 'u.roles LIKE :roles0';
    foreach ($roles as $key => $role){
        if ($key !== 0){
            $condition .= " OR u.roles LIKE :roles".$key;
        }
    }

    $query = $this->createQueryBuilder('u')
        ->where($condition);
    foreach ($roles as $key => $role){
        $query ->setParameter('roles'.$key, '%"'.$role.'"%');
    }

    return $query->getQuery() ->getResult();
}
yavuz16dev
  • 119
  • 6