Buy Access to Course
14.

Dynamic Roles

Share this awesome video!

|

Keep on Learning!

Denying access is great... but we still have a User class that gives every user the same, hardcoded role: ROLE_USER:

95 lines | src/AppBundle/Entity/User.php
// ... lines 1 - 12
class User implements UserInterface
{
// ... lines 15 - 46
public function getRoles()
{
return ['ROLE_USER'];
}
// ... lines 51 - 93
}

And maybe that's enough for you. But, if you do need the ability to assign different permissions to different users, then we've gotta go a little further.

Let's say that in our system, we're going to give different users different roles. How do we do that? Simple! Just create a private $roles property that's an array. Give it the @ORM\Column annotation and set its type to json_array:

112 lines | src/AppBundle/Entity/User.php
// ... lines 1 - 12
class User implements UserInterface
{
// ... lines 15 - 40
/**
* @ORM\Column(type="json_array")
*/
private $roles = [];
// ... lines 45 - 110
}

Tip

json_array type is deprecated since Doctrine 2.6, you should use json instead.

This is really cool because the $roles property will hold an array of roles, but when we save, Doctrine will automatically json_encode that array and store it in a single field. When we query, it'll json_decode that back to the array. What this means is that we can store an array inside a single column, without ever worrying about the JSON encode stuff.

Returning the Dynamic Roles

In getRoles(), we can get dynamic. First, set $roles = $this->roles:

112 lines | src/AppBundle/Entity/User.php
// ... lines 1 - 12
class User implements UserInterface
{
// ... lines 15 - 51
public function getRoles()
{
$roles = $this->roles;
// ... lines 55 - 61
}
// ... lines 63 - 110
}

Second, there's just one rule that we need to follow about roles: every user must have at least one role. Otherwise, weird stuff happens.

That's no problem - just make sure that everyone at least has ROLE_USER by saying: if (!in_array('ROLE_USER', $roles)), then add that to $roles. Finally, return $roles:

112 lines | src/AppBundle/Entity/User.php
// ... lines 1 - 12
class User implements UserInterface
{
// ... lines 15 - 51
public function getRoles()
{
$roles = $this->roles;
// give everyone ROLE_USER!
if (!in_array('ROLE_USER', $roles)) {
$roles[] = 'ROLE_USER';
}
return $roles;
}
// ... lines 63 - 110
}

Oh, and don't forget to add a setRoles() method!

112 lines | src/AppBundle/Entity/User.php
// ... lines 1 - 12
class User implements UserInterface
{
// ... lines 15 - 63
public function setRoles(array $roles)
{
$this->roles = $roles;
}
// ... lines 68 - 110
}

Migration & Fixtures

Generate the migration for the new field:

./bin/console doctrine:migrations:diff

We should double-check that migration, but let's just run it:

./bin/console doctrine:migrations:migrate

Finally, give some roles to our fixture users! For now, we'll give everyone the same role: ROLE_ADMIN:

// ... lines 1 - 22
AppBundle\Entity\User:
user_{1..10}:
// ... lines 25 - 26
roles: ['ROLE_ADMIN']

Reload the fixtures!

./bin/console doctrine:fixtures:load

Ok, let's go see if we have access! Ah, we got logged out! Don't panic: that's because our user - identified by its id - was just deleted from the database. Just log back in.

So nice - it sends us back to the original URL, we have two roles and we have access. Oh, and in a few minutes - we'll talk about another tool to really make your system flexible: role hierarchy.

So, how do I Set the Roles?

But now, you might be asking me?

How would I actually change the roles of a user?

I'm not sure though... because I can't actually hear you. But if you are asking me this, here's what I would say:

$roles is just a field on your User, and so you'll edit it like any other field: via a form. This will probably live in some "user admin area", and you'll use the ChoiceType field to allow the admin to choose the roles for every user:

class EditUserFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder)
    {
        $builder
            ->add('roles', ChoiceType::class, [
                'multiple' => true,
                'expanded' => true, // render check-boxes
                'choices' => [
                    'Admin' => 'ROLE_ADMIN',
                    'Manager' => 'ROLE_MANAGER',
                    // ...
                ],
            ])
            // other fields...
        ;
    }
}

If you have trouble, let me know.

What about Groups?

Oh, and I think I just heard one of you ask me:

What about groups? Can you create something where Users belong to Groups, and those groups have roles?

Totally! And FOSUserBundle has code for this - so check it out. But really, it's nothing crazy: Symfony just calls getRoles(), and you can create that array however you want: like by looping over a relation:

class User extends UserInterface
{
    public function getRoles()
    {
        $roles = [];
        
        // loop over some ManyToMany relation to a Group entity
        foreach ($this->groups as $group) {
            $roles = array_merge($roles, $group->getRoles());
        }
        
        return $roles;
    }
}

Or just giving people roles at random.