Managing users/roles/groups in FOSUserBundle

2019-02-02 17:08发布

问题:

I am developing a simple CRUD to manage users/roles/groups of the application in which I am working. To manage users I'm using FOSUserBundle. What I want to do can be accomplished in several ways:

  • Assigning roles to groups and then assign users to these groups
  • Assigning roles to users directly

But I have no idea how. I knew that FOSUser BaseUser class already has a column roles and in the documentation of FOSUser explains how to establish a ManyToMany relationship between users and groups but do not talk anything about roles. The only idea that comes to mind is to create an entity to manage the roles as well as a form for the same purpose, something like what you see below:

Role Entity

use Symfony\Component\Security\Core\Role\RoleInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Table(name="fos_role")
 * @ORM\Entity(repositoryClass="UserBundle\Entity\Repository\RoleRepository")
 * 
 * @see User
 * @see \UserBundle\Role\RoleHierarchy
 * 
 */
class Role implements RoleInterface
{
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id()
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(name="name", type="string", length=80, unique=true)
     */
    private $name;

    /**
     * @ORM\ManyToOne(targetEntity="Role", inversedBy="children")
     * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true)
     * @var Role[]
     */
    private $parent;

    /**
     * @ORM\OneToMany(targetEntity="Role", mappedBy="parent")
     * @var ArrayCollection|Role[]
     */
    private $children;

    /**
     * @ORM\ManyToMany(targetEntity="User", mappedBy="roles")
     */
    private $users;

    public function __construct($role = "")
    {
        if (0 !== strlen($role)) {
            $this->name = strtoupper($role);
        }

        $this->users = new ArrayCollection();
        $this->children = new ArrayCollection();
    }

    /**
     * @see RoleInterface
     */
    public function getRole()
    {
        return $this->name;
    }

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

    public function setId($id)
    {
        $this->id = $id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }

    public function getUsers()
    {
        return $this->users;
    }

    public function addUser($user, $addRoleToUser = true)
    {
        $this->users->add($user);
        $addRoleToUser && $user->addRole($this, false);
    }

    public function removeUser($user)
    {
        $this->users->removeElement($user);
    }

    public function getChildren()
    {
        return $this->children;
    }

    public function addChildren(Role $child, $setParentToChild = true)
    {
        $this->children->add($child);
        $setParentToChild && $child->setParent($this, false);
    }

    public function getDescendant(& $descendants = array())
    {
        foreach ($this->children as $role) {
            $descendants[spl_object_hash($role)] = $role;
            $role->getDescendant($descendants);
        }
        return $descendants;
    }

    public function removeChildren(Role $children)
    {
        $this->children->removeElement($children);
    }

    public function getParent()
    {
        return $this->parent;
    }

    public function setParent(Role $parent, $addChildToParent = true)
    {
        $addChildToParent && $parent->addChildren($this, false);
        $this->parent = $parent;
    }

    public function __toString()
    {
        if ($this->children->count()) {
            $childNameList = array();
            foreach ($this->children as $child) {
                $childNameList[] = $child->getName();
            }
            return sprintf('%s [%s]', $this->name, implode(', ', $childNameList));
        }
        return sprintf('%s', $this->name);
    }
}

Role Form Type

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class RoleType extends AbstractType {

    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
                ->add('name')
                ->add('parent');
    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Tanane\UserBundle\Entity\Role'
        ));
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'role';
    }    
}

If so what would add to my user form would look something like this

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
            ->add('username', 'text')
            ->add('email', 'email')
            ->add('enabled', null, array(
                'label' => 'Habilitado',
                'required' => false
            ))
            ->add('rolesCollection', 'entity', array(
                'class' => 'UserBundle:Role',
                'multiple' => true,
                'expanded' => true,
                'attr' => array('class' => 'single-line-checks')
            ))
            ->add('groups', 'entity', array(
                'class' => 'UserBundle:Group',
                'multiple' => true,
                'expanded' => true,
    ));
}

But I do not know if it is the right way to handle the roles since in this case would be creating a new table in my DB called fos_roles where were the relationships between users/roles is handled but relationships between groups/roles stay out of it, then that's where I'm a little lost and need help from the more experienced in that tell me and alert if I'm on track and that would make them to achieve what I explain in the first two points. Any advice or help? How do you deal with this?

回答1:

Symfony Roles

The way FOSUserBundle deals with Roles is to store them in the roles column that you've seen, in a serialised format like this: a:1:{i:0;s:10:"ROLE_ADMIN";}. So there's no need for any other tables or entities^.

^ This is in contrast to Groups, which need to be explicitly configured, are represented by a separate Table/Entity, and do involve relating Users to Groups in the DB. Groups let you define arbitrary collections of Roles which can then be given to each User as a discrete bundle.

A User can be a member of any number of Roles. They're identified by strings starting with "ROLE_", you can just start using a new Role.

What the Roles mean for your application is completely up to you, but they're quite a high-level tool - a User is either in a particular Role or they aren't.

You put people in Roles either via the Symfony console:

php app/console fos:user:promote testuser ROLE_ADMIN

Or in PHP:

$user = $this->getUser();
$userManager = $container->get('fos_user.user_manager');
$user->addRole('ROLE_ADMIN');
$userManager->updateUser($user);

And you can test membership in PHP:

$user = $this->getUser();
if ($user->hasRole('ROLE_ADMIN'))
{
    //do something
}

Or using Annotations:

/**
 * @Security("has_role('ROLE_ADMIN')")
 */
 public function adminAction()
 {
     //...

or

/**
 * @Security("has_role('ROLE_ADMIN')")
 */
class AdminController
{
    //...


回答2:

I added the functionality to add default group to the user during registration by overriding the confirmAction in Registration Controller

What I did is I overrided the Registration Controller in my project Bundle by defining the parent to FosUserBUndle .

Then created a function confirmedAction and in the body of the function added this code

$repository = $em->getRepository('AdminAdminBundle:Group');
$group = $repository->findOneByName('staff');

$em = $this->getDoctrine()->getEntityManager();
$user = $this->getUser();
$user->addGroup($group);

$userManager = $this->get('fos_user.user_manager');

$userManager->updateUser($user);

if (!is_object($user) || !$user instanceof FOS\UserBundle\Model\UserInterface) {
    throw new AccessDeniedException('This user does not have access to this section.');
}

return $this->render('FOSUserBundle:Registration:confirmed.html.twig',
                      ['user' => $user]);

And it perfectly saved in db with group assignment.

Hope this will help some one in need as there is little information about the implementation in official fosuserbundle doc.