Chapters
This course is archived!
-
Course Code
Compatible PHP versions: >=5.5.9
Compatible PHP versions: >=5.5.9
- This Video
- Subtitles
- Course Script
Dynamic Roles and Canonical Fields
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
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!
21 Comments
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!
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"
}
}
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!
ah sorry silly me hahaha its worked now thank you hahaha
Haha, great :) Glad it works now!
Cheers!
How do you deny access to a User that hasn't have for example the ROLE "Admin"?
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!
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?
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!
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 !!!
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.
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!
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?
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!
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.
Awesome! I'm happy to hear that you could fix your problem :)
Cheers!
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.
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!
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!
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 :)
"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"
}
}
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"
}
}