Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
TRACK

Symfony 3 >

Dynamic Roles and Canonical Fields

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Let's see what this all looks like in the database! To query the user table, we can actually use the console:

php bin/console doctrine:query:sql 'SELECT * FROM user'

Nice! We inherited a bunch of columns from the base User class, like username, email and enabled. It even tracks our last login. Thanks!

Canonical Fields!?

But there are two weird fields: username_canonical and email_canonical. What the heck? These are one of the more controversial things about FOSUserBundle. Before we explore them, first, just know that when you set username or email, the corresponding canonical field is automatically set for you. So, these canonical fields are not something you normally need to worry or think about.

So why do they exist? Suppose that when you registered, you used some capital letters in your username. The username column will be exactly as you typed it: with the capital letters. But username_canonical will be lowercased. Then, when you login, FOSUserBundle lowercases the submitted username and queries via the username_canonical column.

Why? Because some databases - like Postgresql - are case sensitive. The canonical fields allow a user to login with a case insensitive username - aquanaut in all lowercase, uppercase or any combination.

But mostly... this is just a detail you shouldn't think about. It's all handled for you and other than being ugly in the database, it doesn't hurt anything.

Logging in with Username or Email

And by the way, right now you can only login with your username. If you want to be able to login with username or email, no problem! The documentation has a section about this. Just change your user provider to fos_user.user_provider.username_email.

What does this do? When you submit your login form, the provider section is responsible for taking what you entered and finding the correct User record. Our current user provider finds the User by the username_canonical field. This other one looks up a User by username or email. And you're 100% free to create your own user provider, if you need to login with some other, weird logic. FOSUserBundle won't notice or care.

Dynamic Roles

Check out the database result again and look at roles. I know, it's strange: this is an array field. I'll hold command and click to open the base User class from FOSUserBundle. See, roles holds an array. When you save, it automatically serializes to a string in the database. This is done with the Doctrine array field type.

Notice that even though it's empty in the database, when we login, our user has ROLE_USER. This is thanks to the base User class from FOSUserBundle: it makes sure the User has whatever roles are stored in the database plus ROLE_USER.

Creating an Admin User

Let's try an example of a User that has a different role. Run the console:

php bin/console

Ah, so the bundle comes with a few handy console commands, for activating, creating, promoting and demoting users. Let's create a new one:

php bin/console fos:user:create

How about admin, admin@aquanote.com and password admin.

And now promote it!

php bin/console fos:user:promote

Hmm, let's give admin, ROLE_ADMIN. Ok, try the query again:

php bin/console doctrine:query:sql 'SELECT * FROM user'

Booya! Our new user has ROLE_ADMIN! Quick, go login! Well, logout first, then go login! Use admin and admin. Woohoo! We have both ROLE_USER and ROLE_ADMIN.

In your app, if you want to give different roles to your users, you have 2 options. First, via the command line by using fos:user:promote. If you only have a few users that need special permissions, this is a great option. Or, you can create a user admin area and use the ChoiceType with the 'multiple' => true and 'expanded' => true to select the roles as checkboxes.

Ok, time to squash the ugly and make the FOSUserBundle pages use our layout!

Leave a comment!

21
Login or Register to join the conversation
Default user avatar
Default user avatar Lavin Bassam | posted 4 years ago

so... i got the different result when running this command

php app/console doctrine:query:sql 'SELECT * FROM user'
array(1) {
[0]=>
array(1) {
["current_user"]=>
string(8) "postgres"
}
}

Reply

Hey Lavin,

That's interesting, do you use PostgreSQL? Probably the problem is that "user" might be a reserved word, so try to use a different name for your table, e.g. "app_user". Does it help?

Cheers!

Reply
Default user avatar
Default user avatar Lavin Bassam | Victor | posted 4 years ago

yes im using PostgreSQL i will update the asnwer ASAP if it help and clear the issue

/**
* @ORM\Entity
* @ORM\Table(name="fos_user")
*/

when running the command still got same result
php app/console doctrine:query:sql 'SELECT * FROM user'
array(1) {
[0]=>
array(1) {
["current_user"]=>
string(8) "postgres"
}
}

Reply

Hey Lavin,

Wait, but if you use the different from "user" database name now, you should use the new name in your SQL query :) So, base on your mapping, the query should be:

> php app/console doctrine:query:sql 'SELECT * FROM fos_user'

Cheers!

Reply
Default user avatar
Default user avatar Lavin Bassam | Victor | posted 4 years ago

ah sorry silly me hahaha its worked now thank you hahaha

Reply

Haha, great :) Glad it works now!

Cheers!

1 Reply
Jelle S. Avatar
Jelle S. Avatar Jelle S. | posted 5 years ago

How do you deny access to a User that hasn't have for example the ROLE "Admin"?

Reply

Hey Jelle,

It depends on where you need to do it. In twig templates we can use is_granted() Twig function to check if users have some required role and if they have - show them a link to the admin dashboard for example. But of course, just hiding links from non-admin users is not enough, because some tricky users can guess the admin URL. That's why you can use "secrity.access_control" list in your app/config/security.yml to configure who have access to the URLs with "/admin" prefix for example, etc. Also, in case you love PHP annotations, you can use "@Security()" annotation: https://symfony.com/doc/cur... - for controllers and actions you want to secure with for a special user role. And, of course, if you extend Symfony controller, you can call denyAccessUnlessGranted() method in PHP code to be extra sure things are safe. Also, check this page about Security from Symfony Best Practices: http://symfony.com/doc/curr...

Cheers!

Reply
Default user avatar
Default user avatar Szymon Chomej | posted 5 years ago | edited

Hi.

I created my own form to change users roles.
This is my function in controller:


   /**
     * @Route("/role", name="roleUzytkownika")
     */
    public function roleUzytkownika(Request $request)
    {

        $form = $this->createForm(roleFormType::class);
       
        // only handles data on POST
        $form->handleRequest($request);
        
        if ($form->isSubmitted() && $form->isValid())
         {
            $imie = $form->get('username')->getData();
            $rola =$form->get('roles')->getData();

            $em = $this->getDoctrine()->getManager();

            $user = $em->getRepository('AppBundle:User')->findOneByUsername($imie);

            $user->setRoles($rola);
            $em->flush();

                $this->addFlash('success', '<b>Sukces!!!</b> Zmieniono rolę użytkownika: ' .$form->get('username')->getData().'.');

            return $this->redirectToRoute('roleUzytkownika');
        }

        return $this->render('NarzedziaProdukcyjne/Admin/roleUzytkownika.html.twig', [
            'roleUzytkownikaForm' => $form->createView()
        ]);

    }

Unfortunatelly after submit a have this error: "Call to a member function setRoles() on null" which link to this line of code: $user->setRoles($rola);

Does somebody know what is wrong?

Reply

Hey Szymon,

Looks like your user is equal to null. To debug it, add dump() after:


$user = $em->getRepository('AppBundle:User')->findOneByUsername($imie);
dump($user); die;

For reasons, try to figure out why findOneByUsername() returns null. Probably $imie is empty or holds invalid data, i.e. username that you do not have in DB

Cheers!

Reply
Default user avatar
Default user avatar Szymon Chomej | Victor | posted 5 years ago | edited

Thank you!!!

Problem was with $imie variable. It's object.
I called it in wrong way :)
Of course it should by like that:


$user = $em->getRepository('AppBundle:User')->findOneByUsername($imie->getUsername());

All the best !!!

Reply
Default user avatar
Default user avatar Szymon Chomej | posted 5 years ago | edited

I have another question:)
How to translate labels in form with choice type ?
I dont mean main label but labels for choices.

This is what i have in form builder:


       ->add('roles', ChoiceType::class, [
            'label' => 'form.roles', 'translation_domain' => 'FOSUserBundle',
            'choices' => [
        
                'Admin' => 'ROLE_ADMIN',
                'Jakosc' => 'ROLE_JAKOSC',
            ],
             'label' => 'form.admin', 'translation_domain' => 'FOSUserBundle',
             'label' => 'form.jakosc', 'translation_domain' => 'FOSUserBundle',
            'multiple' => true,
            'expanded' => true
        ]

Ofcourse i added 'Admin' and 'Jakosc' to proper file which is:FOSUserBundle.pl.yml but it doesn't work.

Reply

Hey Szymon Chomej

You are close to achieve it, but instead of defining multiple labels, you can make use of the 'choice_label' option, by setting up a custom callback function as shown here: https://symfony.com/doc/cur...

I hope it helps you :)
Cheers!

Reply
Default user avatar
Default user avatar Szymon Chomej | MolloKhan | posted 5 years ago | edited

Now i ended up with this:


->add('roles', ChoiceType::class, [
            'label' => 'form.roles', 'translation_domain' => 'FOSUserBundle',
           
            'choices' => [
        
                'Admin' => 'ROLE_ADMIN',
                'Jakosc' => 'ROLE_JAKOSC',
            ],
            'choice_label' => function ($value, $key, $index) 
                {
                return 'form.choice.'.$key;
                },
            'required' => true,
            'multiple' => true,
            'expanded' => true,
            'choice_translation_domain' => 'FOSUserBundle', 
            ]
        );

It stil doesnt work:(
I t shows:
'form.choice.Admin'
'form.choice.Jakosc'

Any idea?

Reply

Hey Szymon Chomej!

Actually, I think you are VERY close: you probably just need to add tranlsations for the "form.choice.Admin" and "form.choice.Jakosc" keys. Find/create your FOSUserBundle.en.yml file (or change en for your language), and add these translation keys there. For example:


form.choice.Admin: Admin
form.choice.Jakosc: Quality

Cheers!

Reply
Default user avatar
Default user avatar Szymon Chomej | weaverryan | posted 5 years ago | edited

Hey weaverryan !

Problem solved !!! :)

I have added those keys on the begining to fosuserbundle.pl.yml file
but problem was that i did it with lower case because i was sure it is not case sensitivity. :)

Now i have this in fosuserbundle.pl.yml file:


form:
    choice:
        Admin: 'Administrator'
        Jakosc: 'Menadżer Jakości'

and it works :)

Thank you and all the best.

Reply

Awesome! I'm happy to hear that you could fix your problem :)

Cheers!

Reply
Brent Avatar

Hi Ryan -
I have FOSUserBundle install and it's working great. When a user is logged in there is a route '/profile/edit'. I don't yet see how I can view all users? I want to do is setup routes that 1) Displays all registered users (sort of like I saw in another screencast '/admin/genus' 2) An edit user route for each user (ie. /profile/edit/{id} separate from groups which I currently do not have any groups setup. Is there an event I can override or service I need to create? Any tip/ideas on how I might accomplish this would be greatly appreciated.

Reply

Hey Brent

The route '/profile/edit' is only for *that* logged in user, what you need is an *admin* section so you can manage all your users, something similar as you said about 'admin/genus', but 'admin/users/{id}' instead. Maybe EasyAdminBundle can help you on this, is easy to use and we have a fresh tutorial about it https://knpuniversity.com/s...

Cheers!

Reply
Brent Avatar

Where I needed to add user management was already an admin site so EasyAdminBundle wasn't an option this time. I did get this working by extending the ProfileFormType as BaseRegistrationFormType, removed 'current_password' from the form, added methods to extend the fos user manager, little bit of wrangling and got it to suite my needs for user management. Though, I will look to use EasyAdminBundle in the future.

Thank you!

Reply

Ohh, so you are a pro now extending a bundle's behaviour

When you get a chance, give it a try to EasyAdminBundle, it is great!

Have a nice day :)

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.3.*", // v3.3.18
        "doctrine/orm": "^2.5", // v2.7.0
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.5
        "symfony/swiftmailer-bundle": "^2.3", // v2.5.4
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.3.0
        "sensio/distribution-bundle": "^5.0", // v5.0.18
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.25
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "knplabs/knp-markdown-bundle": "^1.4", // 1.5.1
        "doctrine/doctrine-migrations-bundle": "^1.1", // v1.3.2
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "friendsofsymfony/user-bundle": "^2.0" // v2.0.0
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.4
        "symfony/phpunit-bridge": "^3.0", // v3.2.7
        "nelmio/alice": "^2.1", // v2.3.1
        "doctrine/doctrine-fixtures-bundle": "^2.3", // v2.4.1
        "symfony/web-server-bundle": "^3.3"
    }
}
userVoice