Symfony 4 Doctrine, How to load user Roles from the Database
Solution 1
So far, you haven't mapped your User to Roles as per your database structure.
private $roles;
Has no information about how it maps to the roles table. It should look something like:
/**
* @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;
You'll also need to create an initial set of roles in construct so that getRoles
doesn't throw an error and so that roles can be added to new users one by one if needed:
public function __construct()
{
$this->isActive = true;
$this->roles = new ArrayCollection();
}
You can delete getRole()
and setRole()
because we don't have a single role (unless it's required by the interface), and you can lose the current $role
property:
/**
* @ORM\Column(type="string", length=254, nullable=true)
*/
private $role;
but add a setter that takes a collection:
public function setRoles(Collection $roles)
{
$this->roles = $roles;
}
Then to get the Roles:
public function getRoles()
{
return $this->roles->toArray();
}
If you're using a form to create user (especially a Sonata Admin one), you may use the following methods in addition to add and remove single Roles from a User (it will remove the relationship between the user and the role, not the role itself):
public function addRole(Role $role)
{
$this->roles->add($role);
}
And one to remove a role:
public function removeRole(Role $role)
{
$this->roles->removeElement($role);
}
Solution 2
The scenario of having 3 tables (users / roles / user_roles) is so common that it should be documented in the manuals.
In my case, to make it work, I applied the answer of "OK sure", then I hit the problem signaled by "Vasiliy Toporov" and "Radu". $this->roles->toArray() is not enough in getRoles(), because it returns an array of Role entity, instead of the expected array of strings (expected by Symfony\Component\Security\Core\Authentication\Token\AbstractToken).
To make it work, I first added in the Role entity class (rlCode = the string code; ROLE_ADMIN etc):
public function __toString(): string
{
return $this->rlCode;
}
Then, in the User entity class I changed getRoles() to:
public function getRoles()
{
$arrRolesString = [];
foreach($this->roles->getIterator() as $i => $item) {
$arrRolesString[] = (string)$item;
}
return $arrRolesString;
}
Now it works. The next problem I am having, however, is that all multiple roles assigned to an user are a duplicate of the first role retrieved by the join query, and I have no idea why (the join query returns all roles correctly, but it must be a problem in the ManyToMany assignation somewhere...if anyone knows, please tell)
EDIT: please ignore the duplicate issue. It was because Doctrine make entity maps tinyint (my id column in the Roles table is tinyint) as boolean, instead of integer.
Comments
-
Sanjok Gurung almost 2 years
New to Symfony. How do I load the current loggedin user's role from the database using Doctrine. I have 3 tables laid out like so.
users
=> (user_id, username, password, email
)user_roles
=> (id,user_id,role_id
)roles
=> (role_id, role_name
)I have entities and their corresponding repositories for each table.
My security.yaml looks like this.
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
My
Entity/User
implementsUserInterface
component and by reading documents I came to know that thegetRoles()
method is responsible for updating user roles. I have created a custom method calledgetUserRoles($id)
in myUserRolesRepository.php
where I managed to return string array of the current user's roles however I am not able to access this method from theEntity
. I know I should not access Repository methods from an Entity class, but I am dearly stuck at this stage. So for now mygetRoles()
method returns static arrayreturn array('ROLE_ADMIN', 'ROLE_EDITOR');
My
User
Entity Classnamespace 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; } }
-
Sanjok Gurung about 6 yearsThank you, I am actually only relying on Symfony's built in role system. The only problem I am having is that my roles are in a table and I need to access the currently logged in user's role/s from
getRoles()
in my User entity. -
Sanjok Gurung about 6 yearsThank you for this brilliant answer, We have another external form using Idiorm to register users, which will populate the users,user_role,roles tables. I do not explicitly need to use
addRole
andremoveRole
do I?? I mean I can manage externally if I want to cant I?? -
OK sure about 6 yearsIf you don't need them, then they can be removed for sure. So long as you're creating an ArrayCollection to put into the
setRoles
method from the form you should be fine:new ArrayCollection($rolesArray);
-
Sanjok Gurung about 6 yearsJust to understand, if I were to revoke a role from a user
removeRole(Role $role)
would remove the actual role from the roles table wouldn't it. if I wanted to remove only the roles from the user, for instance in my case I would only like to removerole_id
user_id
from heuser_roles
table. WouldremoveRole
achieve that?? -
OK sure about 6 yearsRemoving the role using the
removeRole
method above wouldn't remove the actual role from the roles table but would remove the relationship between that user and the role. -
Sanjok Gurung about 6 yearsExcellent, Thank you.
-
Vasiliy Toporov over 5 yearsCustom Role entity should extends Symfony\Component\Security\Core\Role\Role because RoleInterface was deprecated and removed from Symfony 4. Without this extending you will get exception in Symfony\Component\Security\Core\Authentication\Token constructor.
-
Radu about 5 yearsI think that returning $this->roles->toArray(); in getRoles() is not enough because it returns an array of objects which won't satisfy PostAuthenticationGuardToken::__constructor(). The 4th $roles parameter needs to be an array with string values.
-
Prawny almost 5 yearsWhy is none of this even hinted in the documentation?