FOS 捆绑包 - 如何选择具有特定角色的用户?

Posted

技术标签:

【中文标题】FOS 捆绑包 - 如何选择具有特定角色的用户?【英文标题】:FOS bundle - How to select users with a specific role? 【发布时间】:2012-02-19 11:24:29 【问题描述】:

我正在使用 FOS 捆绑包,我想从数据库中检索具有给定角色的所有用户。

最好的方法是什么?

【问题讨论】:

在数据库中,角色字段是一个序列化数组,如:a:2:i:0;s:10:"ROLE_ADMIN";i:1;s:9:"ROLE_USER"; 方法和序列化目前在本期讨论:github.com/FriendsOfSymfony/FOSUserBundle/issues/1308 【参考方案1】:

只需将其添加到您的 UserRepository 或将 $this->_entityName 替换为 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();

如果您使用 FOSUser 组,您应该使用:

/**
 * @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();

【讨论】:

此解决方案可能会冒着获取 wring 用户的风险,例如,如果某些用户具有角色 ROLE_ADMIN 而某些用户具有角色 ROLE_SUPER_ADMIN 并且您运行 $repository->findByRole('ADMIN') 上述将匹配具有 ROLE_ADMIN 和 ROLE_SUPER_ADMIN 的两个用户。除此之外,由于角色层次结构(symfony.com/doc/current/book/security.html#hierarchical-roles),您不会获得具有隐含角色的用户。 我刚刚进行了更正,以解决您担心角色混淆的问题。除此之外,它要求一个>特定 如果您不必检查角色层次结构,这个答案是可以的。如果你也必须检查子角色,这个答案是没用的。 如果你有很多用户,这个解决方案在性能方面可能会很昂贵。示例:#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%' ; 我必须更改 setParameter 上的引号才能使其正常工作:->setParameter('roles', "%" . $role . "%")【参考方案2】:

好吧,如果没有更好的解决方案,我想我会去一个DQL查询:

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

$users = $query->getResult();

【讨论】:

【参考方案3】:

如果您有此要求并且您的用户列表会很广泛,那么您将遇到性能问题。我认为您不应该将字段中的角色存储为序列化数组。您应该创建一个实体角色以及与用户表的多对多关系。

【讨论】:

是的,这可能是最好的解决方案,但我必须编写自己的用户管理!【参考方案4】:

正如@Tirithen 所说,问题在于由于角色层次结构,您不会获得具有隐式角色的用户。但是有办法解决这个问题!

Symfony 安全组件提供了一项服务,为我们提供特定父角色的所有子角色。我们可以创建一个几乎做同样事情的服务,只是它为我们提供给定子角色的所有父角色。

创建一个新服务:

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;
    

例如在 yaml 中定义您的服务并将角色层次结构注入其中:

# 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%"]

现在您可以在自己的服务中使用该类了。通过调用$injectedService->getParentRoles(['ROLE_YOUR_ROLE']);,您将获得一个数组,其中包含将导致“ROLE_YOUR_ROLE”权限的所有父角色。查询具有其中一个或多个角色的用户...利润!

例如,当您使用 MongoDB 时,您可以向您的用户文档存储库添加一个方法:

/**
 * Find all users with a specific role.
 */
public function fetchByRoles($roles = [])

    return $this->createQueryBuilder('u')
        ->field('roles')->in($roles)
        ->sort('email', 'asc');

我不喜欢 Doctrine ORM,但我相信它不会如此不同。

【讨论】:

这是一个简洁的答案,但是您如何将新服务与UserRepository 一起使用?您是否将存储库转换为服务? @PeterB 在控制器中的示例:$roles = $this->get('foo.bar.reversed_role_hierarchy')->getParentRoles('ROLE_ADMIN'); $users = $userRepo->fetchByRoles($roles) 顺便说一句,对于 Doctrine ORM,我目前发现的唯一方法是通过此捆绑包 github.com/beberlei/DoctrineExtensions 和存储库 $qb->where("REGEXP(u.roles, '".implode('|',$roles)."') = 1"); 使用正则表达式【参考方案5】:

你可以在你的 DQL 上使用这个:

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

当然使用 QueryBuilder 会更优雅:

// $role = 'ROLE_YOUR_ROLE';
$qb->where('u.roles [NOT] LIKE :role')
   ->setParameter('role', "%$role%");

【讨论】:

【参考方案6】:

我终于解决了,下面是一个确切的解决方案:

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();

干杯!

【讨论】:

这看起来很有趣。 formdata 是从哪里来的? @apfz - 刚刚更正它,感谢您注意到它。正如您所看到的 $formData 正在通过参数..【参考方案7】:

如果您需要使用 YAML 文件中的 DQL 过滤器按角色过滤用户(例如在 EasyAdminBundle 中)

entities:
    Admin:
        class: App\Entity\User
        list:
            dql_filter: "entity.roles LIKE '%%ROLE_ADMIN%%'"

【讨论】:

以上是关于FOS 捆绑包 - 如何选择具有特定角色的用户?的主要内容,如果未能解决你的问题,请参考以下文章

FOS 捆绑用户 - 多个角色

Laravel最好的Auth捆绑包是什么? [关闭]

Symfony 2 FOS 用户捆绑引导模式 AJAX 登录

在成员资格提供者中动态选择角色

如何自定义 FOS UserBundle URL

在 Mongoose 中,我有具有一对多关系的用户和角色模式。如何查询特定用户是不是具有“管理员”角色?