Dynamic Roles and Canonical Fields

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!

  • 2018-01-09 Diego Aguiar

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


  • 2018-01-08 Szymon Chomej

    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:

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

    and it works :)

    Thank you and all the best.

  • 2018-01-08 weaverryan

    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


  • 2018-01-07 Szymon Chomej

    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:

    Any idea?

  • 2018-01-04 Diego Aguiar

    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 :)

  • 2018-01-04 Szymon Chomej

    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.

  • 2018-01-04 Szymon Chomej

    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 !!!

  • 2018-01-04 Victor Bocharsky

    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


  • 2018-01-04 Szymon Chomej


    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

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


    $this->addFlash('success', 'Sukces!!! 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?

  • 2017-12-25 Victor Bocharsky

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


  • 2017-12-22 Jelle Schouwstra

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

  • 2017-07-21 Diego Aguiar

    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 :)

  • 2017-07-20 brentmtc

    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!

  • 2017-07-19 Diego Aguiar

    Hey brentmtc

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


  • 2017-07-18 brentmtc

    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.