简体   繁体   中英

Symfony 4 Doctrine, How to load user Roles from the Database

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;
}
}

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

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:

  1. 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.

  2. 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).

  3. 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.

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

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM