Reputation: 1023
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
implements UserInterface
component and by reading documents I came to know that the getRoles()
method is responsible for updating user roles. I have created a custom method called getUserRoles($id)
in my UserRolesRepository.php
where I managed to return string array of the current user's roles however I am not able to access this method from the Entity
. I know I should not access Repository methods from an Entity class, but I am dearly stuck at this stage. So for now my getRoles()
method returns static array return array('ROLE_ADMIN', 'ROLE_EDITOR');
My User
Entity Class
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;
}
}
Upvotes: 5
Views: 14097
Reputation: 323
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.
Upvotes: 1
Reputation: 2194
This is the solution to relate 2 tables with several users in Symfony 4
//Entity Usuarios
/**
* @var \Role
*
* @ORM\ManyToOne(targetEntity="Role")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="Role_id", referencedColumnName="id")
* })
*/
private $role;
And this is the method getRoles()
public function getRoles()
{
//return array('ROLE_USER');
return array($this->role->getRol());
}
In this way the array returns the Role of the logged in user
Upvotes: 0
Reputation: 2646
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);
}
Upvotes: 6
Reputation: 1828
Symfony roles work very closely with security voters. If you change the standard way of doing role management in Symfony (not use ROLE_SOMETHING), then you should do three things:
Map the getRoles
getter to return a collection of Roles objects in your User class. Using doctrine, you can map the relationship to a ManyToMany easily.
Then you have to create a GuardAuthenticator and override the createAuthenticatedToken
method, so you pass the custom roles you made to Symfony's Auth Token (which is the class responsible for the access control in your application).
You have to implement a security voter that can fetch the roles from the database and vote if a user can or not do a certain thing.
As you can see, all this is very complicated, but not impossible. Symfony's built-in role system is more than enough to cover your needs I think.
Upvotes: 0