Symfony 4 Doctrine,如何从数据库加载用户角色

5
新手使用Symfony。我该如何使用Doctrine从数据库加载当前已登录用户的角色?我的三张表格如下所示。 users => (user_id, username, password, email) user_roles => (id, user_id, role_id)
roles => (role_id, role_name)
我为每个表格拥有相应的实体和仓库。我的security.yaml文件如下。
security:
encoders:
    App\Entity\User:
        algorithm: bcrypt

providers:
    our_db_provider:
        entity:
            class: App\Entity\User
            property: username
            # if you're using multiple entity managers
            # manager_name: customer

firewalls:
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false
    main:
#            pattern: ^/
#            http_basic: ~
        provider: our_db_provider
        anonymous: true
        form_login:
            #login_path is GET request used ti display the form
            # needs to be route names(alias) not the path.
            login_path: login

            #check_path is a POST request
            check_path: logincheck
            use_forward: true

            default_target_path: default
            always_use_default_target_path: true

我的 Entity/User 实现了 UserInterface 组件, 通过阅读文档我知道 getRoles() 方法负责更新用户角色。 我在我的 UserRolesRepository.php 中创建了一个自定义方法叫做getUserRoles($id), 在其中我成功返回了当前用户角色的字符串数组, 但是我无法从Entity访问此方法。我知道不应该从 Entity 类中访问 Repository 方法,但是我卡在这个阶段。所以目前我的 getRoles() 方法返回静态数组 return array('ROLE_ADMIN', 'ROLE_EDITOR');

我的 User Entity 类:

namespace App\Entity;

use App\Repository\UserRepository;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use App\Repository\UserRolesRepository;
use Doctrine\ORM\EntityRepository;
use App\Services\Helper;

/**
 * @ORM\Table(name="`user`")
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 */
class User implements UserInterface, \Serializable
{
    /**
    * @ORM\Id()
    * @ORM\GeneratedValue()
    * @ORM\Column(type="integer")
    */
private $id;

/**
 * @ORM\Column(type="string", length=25, nullable=true)
 */
private $username;

/**
 * @ORM\Column(type="string", length=64, nullable=true)
 */
private $password;

/**
 * @ORM\Column(type="string", length=254, nullable=true)
 */
private $email;

/**
 * @ORM\Column(type="boolean", nullable=true)
 */
private $isActive;

private $roles;

/**
 * @ORM\Column(type="string", length=254, nullable=true)
 */
private $role;

public function __construct()
{
    $this->isActive = true;
}

/**
 * @return mixed
 */
public function getRole()
{
    return $this->role;
}

/**
 * @param mixed $role
 */
public function setRole($role)
{
    $this->role = $role;
}

public function getId()
{
    return $this->id;
}

public function getUsername(): ?string
{
    return $this->username;
}

public function setUsername(?string $username): self
{
    $this->username = $username;

    return $this;
}

public function getPassword(): ?string
{
    return $this->password;
}

public function setPassword(?string $password): self
{
    $this->password = $password;

    return $this;
}

public function getEmail(): ?string
{
    return $this->email;
}

public function setEmail(?string $email): self
{
    $this->email = $email;

    return $this;
}

public function getIsActive(): ?bool
{
    return $this->isActive;
}

public function setIsActive(?bool $isActive): self
{
    $this->isActive = $isActive;

    return $this;
}


//return is required or else returns an fatal error.
public function getRoles()
{
    return array('ROLE_ADMIN','ROLE_EDITOR');
}

public function eraseCredentials()
{
    // TODO: Implement eraseCredentials() method.
}

public function serialize()
{
    // TODO: Implement serialize() method.
    return serialize(array(
        $this->id,
        $this->username,
        $this->password,
    ));
}

/** @see \Serializable::unserialize() */
public function unserialize($serialized)
{
    list (
        $this->id,
        $this->username,
        $this->password,
        // see section on salt below
        // $this->salt
        ) = unserialize($serialized, ['allowed_classes' => false]);
}

public function getSalt()
{
    // TODO: Implement getSalt() method.
    return null;
}
}

我认为你需要看一下如何在Doctrine中设置多对多关系。但是只有在真正需要按角色查询时才这样做。大多数情况下,角色只是直接存储在实体表中的数组。 - Cerad
你能否在你的User实体类中包含$roles属性和Doctrine注释? - OK sure
@OKsure,谢谢。我已经添加了$roles属性,但您能否告诉我Doctrine注释应该指向哪里和什么? - Sanjok Gurung
@SanjokGurung,我的解释和示例有助于解决你的问题吗? - OK sure
@OKsure 谢谢,非常棒,完全符合我的要求。 - Sanjok Gurung
4个回答

6

到目前为止,您还没有按照数据库结构将用户映射到角色。

private $roles;

没有关于它如何映射到角色表的信息。它应该看起来像这样:

/**
 * @var Collection|Role[]
 * @ORM\ManyToMany(targetEntity="Role")
 * @ORM\JoinTable(
 *      name="user_roles",
 *      joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
 *      inverseJoinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")}
 * )
 */
private $roles;

您还需要在构造函数中创建一组初始角色,以便getRoles不会抛出错误,并且如果需要,可以逐个向新用户添加角色:
public function __construct()
{
    $this->isActive = true;
    $this->roles = new ArrayCollection();
}

您可以删除getRole()setRole() 方法,因为我们没有单一的角色(除非接口需要),并且可以移除当前的$role属性。
/**
 * @ORM\Column(type="string", length=254, nullable=true)
 */
private $role;

但是添加一个接受集合的setter方法:
public function setRoles(Collection $roles)
{
    $this->roles = $roles;
}

然后获取角色:
public function getRoles()
{
    return $this->roles->toArray();
}

如果您正在使用表单创建用户(特别是Sonata Admin表单),除了添加和删除单个角色之外,您还可以使用以下方法来操作用户角色关系(它会删除用户与角色之间的关系,而非角色本身):
public function addRole(Role $role)
{
    $this->roles->add($role);
}

还有一个用于删除角色的方法:

public function removeRole(Role $role)
{
    $this->roles->removeElement($role);
}

非常感谢您提供这个精彩的答案。我们有另一个使用Idiorm注册用户的外部表单,它将填充users、user_role和roles表。我不需要显式地使用addRoleremoveRole,对吗?我的意思是,如果我想要管理外部数据,我可以自己处理,对吧? - Sanjok Gurung
如果您不需要它们,那么它们可以被删除。只要您创建一个ArrayCollection并将其放入从表单传递给setRoles方法的数组中,一切都应该没问题:new ArrayCollection($rolesArray); - OK sure
只是为了理解,如果我要从用户中撤销一个角色 removeRole(Role $role),它会从角色表中删除实际的角色,对吗?如果我只想从用户中删除角色,例如在我的情况下,我只想从 user_roles 表中删除 role_id user_id。那么 removeRole 能实现这个吗? - Sanjok Gurung
1
自定义角色实体应该扩展Symfony\Component\Security\Core\Role\Role,因为RoleInterface已经被弃用并从Symfony 4中删除。如果没有这个扩展,您将在Symfony\Component\Security\Core\Authentication\Token构造函数中得到异常。 - Vasiliy Toporov
1
我认为在getRoles()中返回$this->roles->toArray()是不够的,因为它返回一个对象数组,这将无法满足PostAuthenticationGuardToken::__constructor()的要求。第四个$roles参数需要是一个包含字符串值的数组。 - Radu
显示剩余3条评论

1
拥有3个表格(用户/角色/用户角色)的情况非常普遍,应该在手册中进行记录。
在我的情况下,为了使其正常运行,我采用了“OK sure”的答案,然后遇到了“Vasiliy Toporov”和“Radu”指出的问题。在getRoles()中,$this->roles->toArray()不够用,因为它返回一个Role实体数组,而不是Symfony\Component\Security\Core\Authentication\Token\AbstractToken所期望的字符串数组。
为了使其正常工作,我首先在Role实体类中添加了以下内容(rlCode = 字符串代码; ROLE_ADMIN等):
public function __toString(): string
{
    return $this->rlCode;
}

然后,在用户实体类中,我将getRoles()方法更改为:
public function getRoles()
{   
    $arrRolesString = [];

    foreach($this->roles->getIterator() as $i => $item) {
        $arrRolesString[] = (string)$item;
    }
    return $arrRolesString;        
}

现在它可以工作了。然而,我遇到的下一个问题是,分配给用户的所有多个角色都是联接查询检索到的第一个角色的副本,我不知道为什么(联接查询正确返回所有角色,但一定是在ManyToMany分配中出了问题...如果有人知道,请告诉我)。
编辑:请忽略重复的问题。这是因为Doctrine将实体映射tinyint(我的Roles表中的id列是tinyint)作为布尔值,而不是整数。

0

Symfony的角色与安全投票者密切相关。如果您更改了Symfony中的角色管理标准方式(不使用ROLE_SOMETHING),则应执行以下三个操作:

  1. 将User类中的getRoles getter映射为返回Roles对象集合。使用Doctrine,您可以轻松地将关系映射到ManyToMany。

  2. 然后,您必须创建一个GuardAuthenticator并覆盖createAuthenticatedToken方法,以便将您制作的自定义角色传递给Symfony的Auth Token(这是负责访问控制的类)。

  3. 您必须实现一个安全投票者,该投票者可以从数据库中获取角色并投票,以确定用户是否可以执行某些操作。

正如您所看到的,所有这些都非常复杂,但并非不可能。我认为Symfony内置的角色系统已经足够满足您的需求。


谢谢,实际上我只依赖于Symfony内置的角色系统。我遇到的唯一问题是我的角色在一个表中,我需要从我的用户实体的getRoles()方法中访问当前登录用户的角色。 - Sanjok Gurung

0

这是在Symfony 4中将2个表与多个用户相关联的解决方案

//Entity Usuarios
/**
     * @var \Role
     *
     * @ORM\ManyToOne(targetEntity="Role")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="Role_id", referencedColumnName="id")
     * })
     */
    private $role;

这是getRoles()方法

public function getRoles()
{
   //return array('ROLE_USER');
   return array($this->role->getRol());
}

通过这种方式,数组返回已登录用户的角色


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接