Introducing roles concept0:00
Now, in these last few episodes, we've reviewed the essentials of using the gate component. But now what about when you want to take it a step further and have this concept of roles in your application? How do you do that? And once again, as with most things, it just sort of comes down to, well, you can do it however you want to. It really depends upon the scope of your application. So for example, if you wanted to, if I had this sidebar, you could store your roles directly in your codebase. For example, maybe you could have a, maybe a role singleton.
in your codebase. For example, maybe you could have a, maybe a Role singleton. And then within here, you could define your different roles directly in your codebase. Something like manager with a label. A label is like what the user would see on the settings page. Maybe you have a manager and an editor. You get the idea. Now with this approach, you get to store this stuff directly in version control. And then as for your table, you would have a very simple hasMany relationship for your users.
And then as for your table, you would have a very simple hasMany relationship for your users. Or if your User can't have many roles, they can have one Role, then it could be a belongsTo relationship. For simple stuff, that would be possibly the way to go. And then of course, you could do like we did before, like editForum. Maybe that's a permission that'll accept the user. And here you simply return user and you would create a role like hasRole and then reference it here. Maybe only managers have the ability or rights to edit the forum.
it here. Maybe only managers have the ability or rights to edit the forum. Now it's important to remember the Laravel's ACL component, it doesn't have any awareness of roles. So whatever you do, you'll just need to set that part up on your own and then reference it within your closure here. So yeah, that would be one option. Another option would be to store all of your roles and your permissions directly in the database. And this will make things a little more configurable, a little more dynamic.
Design database-backed roles1:45
database. And this will make things a little more configurable, a little more dynamic. If you can imagine building a CMS like WordPress, this is probably the way you would want to tackle it. So let me show you what that might look like. But to get started, well, we need this concept of a Role and also a Permission. Okay, make a model for a Permission. And then another one for a Role that a User can have. Now remember, a Role would be like editor or manager, and a Permission would be specific things like edit the forum, that gives you the rights to edit somebody's reply or delete
Now remember, a role would be like Editor or Manager, and a permission would be specific things like edit the forum, that gives you the rights to edit somebody's reply or delete a thread. Maybe another permission would be manage money. A Manager can manage the money, but the Editor, no, they don't have that permission level. Next we need the migrations, right? So make a migration. Let's store it all within one file. We'll call it create_roles_tables. And then if we switch over to that, if I scroll down right here, let's do this, Schema::create(
Create roles migrations2:38
We'll call it createRolesTables. And then if we switch over to that, if I scroll down right here, let's do this, schema, create, and let's begin with the roles table, that'll accept our table variable here. Okay, so this is very simple. We would, of course, have an auto-incrementing ID. Next, we would have a section for the name of the role, like manager or editor. And then we might also want to have some kind of label here, like we were talking about earlier, something that the user can actually read on the settings page. So a, I'm sorry, not human, a label column would make sense, but we won't require it. Finally, why don't we add the timestamps and let's be done with that table.
So a, I'm sorry, not human, a label column would make sense, but we won't require it. Finally, why don't we add the timestamps and let's be done with that table. Very, very simple. Next, we need to do another one for our various permissions. And this will be pretty similar, actually. A permission has a name and a permission has a readable label. So if the name was editForum, then the label might be editTheForum or something like that, whatever you want it to do. Next, we need a few joining tables, because if you think about it, yes, we have a concept of roles, and yes, we have permissions, but if we have, for example, an admin role, well,
Next, we need a few joining tables, because if you think about it, yes, we have a concept of roles, and yes, we have permissions, but if we have, for example, an Admin role, well, what permissions does that Administrator have? We need a linking table or a pivot table to create that connection. So let's do that now. Let's grab that. And we're going to follow that convention of the two table names, we make them singular, and then we order them in alphabetical order. So this would be permission_role. All right.
So this would be permission role. All right. So what do we need here? Well, first, we need an integer for the permission_id, and we do need that to be positive. And then we need another column for the role_id. This is the connection. We have the ID of the permission and the ID of the role. And that way, we can link the two together. And then finally, we just want some foreign constraints. So for example, foreign permission_id, what does that reference?
And then finally, we just want some foreign constraints. So for example, foreign permission_id, what does that reference? Well, that references the ID column on the permissions table. And if I was to delete that permission, I want to cascade down and also delete this row as well. And then we'll do the exact same thing for the next one here for the role_id. That references the ID column on the roles table. So now we have a link between a role and a permission, but we just need one more link. What about the connection between a User and a Role? If my User is named John and we have a Role called editor, well, that Role doesn't belong
What about the connection between a User and a Role? If my User is named John and we have a Role called editor, well, that Role doesn't belong to John. John, Jane, and Jack could also be editors. So this is a belongsToMany relationship we're working with as well. So let's set this up. We have a relationship between a User and a Role. Okay, so let's just replace these here, the user_id as well as the role_id. The role_id references the ID on roles, and then the user_id references the ID on users. Okay, does that make sense?
The role ID references the ID on roles, and then the user ID references the ID on users. Okay, does that make sense? If I want to give John a role of editor and manager at the same time, we have the ability to do that now. Finally, we should do one more thing here. Do we want a primary key? Well, for these linking tables, it's pretty common to do something like this. We could say table primary, and what makes this unique would be the connection of the permission ID as well as the role ID. And now the same thing would be true down here.
permission ID as well as the role ID. And now the same thing would be true down here. In this case, it would be the role ID and the user ID. Okay, great. So we're ready to migrate this, but what I have here is a fresh installation of Laravel. So let's go to my config/database.php file. We will use MySQL in this section, so we just need to update our settings. So if you've worked with Laravel, you know we can edit our .env file, and I've already created a database for us called lesson. So why don't we try this out?
already created a database for us called lesson. So why don't we try this out? Let's migrate our database. Let's go into my VM and go into our roles folder, and I will migrate the database. Great. So if I switch to SQL Pro, I already have this pulled up. Let's give it a refresh, and there we go. We have roles, we have permissions, we have a link between a permission and a role, and we have a link between a user and a role. So we're going great.
Define Eloquent relationships6:59
we have a link between a User and a Role. So we're going great. The next step is we need to create these relationships here. So let's close these out. Let's go into my Role, and let's say we have a role object, and we want to say, well, what permissions are associated with this role? This is what we want here. Okay, well, let's set that up. Let's hide the sidebar, and we'll say permissions, and our relationship here is a belongsToMany, like so.
Let's hide the sidebar, and we'll say permissions, and our relationship here is a belongsToMany, like so. Great. Let's do the exact same thing for the permission. So in this case, we have a permission like, how about edit forum? Well, if I want to say which roles have the right to take this action, then we can use this relationship here. Okay, great. So let's paste that in, roles, and we'll reference the Role class here. Next, though, if I switch back to Role, what about one other thing?
So let's paste that in, roles, and we'll reference the Role class here. Next, though, if I switch back to role, what about one other thing? We've learned that we can create a Role and create a Permission, but when it's time to link those two together, with Eloquent, what we do is role->permissions, and we will save or potentially sync the given Permission. For example, EditForum Permission object. That's what we would do here. But you know what? I think what we're really doing, if we describe it a little better, is we're saying role, give permission to edit the forum.
I think what we're really doing, if we describe it a little better, is we're saying role, give permission to edit the forum. So with that in mind, maybe we should create a helper method here, like so. We will accept a permission object, and then here is where we will reference that. This permissions, save, or sync again, if you want to sync everything up and delete what's necessary, use sync. Okay, so that looks good to me. Finally, we just have one last one. What about the relationship between a User and a Role? So if I have my User object, and I want to figure out which roles does the User have,
What about the relationship between a User and a Role? So if I have my User object, and I want to figure out which roles does the User have, this is what we do. And once again, a belongsToMany relationship, like so. And then what I presume is later, we'll add a couple more. So for example, if I want to say User, well, maybe I want to check if they have a Role. So for example, does this User have a Role of manager? Well, we could add a method for that. Another one would be, well, what if I want to assign a Role to a User? Once again, we would say User roles, save Role, or really we could say User assignRole,
Another one would be, well, what if I want to assign a role to a User? Once again, we would say userRoles, saveRole, or really we could say userAssignRole, and then pass that in, or maybe even something like actAs. And that way, we could even reference a string here. So for example, if I said User, I want you to actAs the manager that will assign the proper role to the User. So we can work that however we want, but I think this is good for now. So why don't we switch over to php artisan tinker and see if we can get everything to work properly. php artisan tinker, I'm going to switch my namespace to app, and that way I can reference.
work properly. php artisan tinker, I'm going to switch my namespace to app, and that way I can reference any of my Eloquent classes very easily. So right off the bat, let's build up a User. Okay, so we have this Johnny guy, and actually I should save that. Okay, next I need a role. So let's build up a Role where the name, this will be the manager, and the label will be site manager. Again, that is what you would see on the settings page itself if you want more of a readable description of the role.
Alright, let's save that one. Cool. So quick recap. We have a User, we have a Role, and we have a Permission, but right now we have no link between this Role and this Permission. Let's do that now. I can say role, and don't forget, in Sublime, we added this helper method right here. givePermissionTo. So let's do this. Role, givePermissionTo, edit the form.
So let's do this. Role, give permission to, edit the form. Done. So now what you'll see is we have updated this pivot table. Right there. And this is how we create the link between a Role and what permissions it has. In this case, the Role with an ID of one, the manager, has permission to do the permission with an ID of one, which is edit the form. Now later, we will assign the Role to the User, but I want to hold off on that. So the next step is to go back to our auth service provider and hook it up with Laravel's
Register permissions with Gate11:42
Now later, we will assign the role to the user, but I want to hold off on that. So the next step is to go back to our AuthServiceProvider and hook it up with Laravel's Gate component. Here's what we might do. If we want to hook into it, because remember, before, we were storing the permissions directly here. So things like edit form were stored here. But now we're doing it in a table. So if we want to hook into everything, well, maybe we could do this. Maybe something like, let's go ahead and import this.
So if we want to hook into everything, well, maybe we could do this. Maybe something like, let's go ahead and import this. Use app\Permission. Let's just fetch all of our permissions. This, getPermissions, as permission. And then for each one, we will register it with the Gate class. And that will accept the name of the permission and trigger the closure. Okay, so now our test will just be whether the User has the given role. But what role would it be? Well, it would be, does the User have any of the roles that are associated with this
But what role would it be? Well, it would be, does the User have any of the roles that are associated with this given permission? So for example, if a permission is editForm, and the roles that can perform that permission are manager, well, we just want to see, does the User have the role of manager? So let's pass those through. Permission roles. Okay, so let's set this up. Let's create our protected method for getPermissions. And that will simply return permission.
Let's create our protected method for getPermissions. And that will simply return permission. Let's eager load the roles and fetch all of them. All right, so for each permission, we will define or register a new permission with Laravel's Gate class. And for each one, we're just going to see if the user has the proper role. Now, before this will work, of course, we need a hasRole method. So let's do that down here at the bottom, hasRole. And now, well, here's what we'll do. We will allow the user to reference a string.
And now, well, here's what we'll do. We will allow the User to reference a string. So user has role of manager. I definitely want to allow that. But we've also done it in this case where we pass through a collection of roles. So we need to allow for both scenarios. I think this should be easy. Well, let's say, for example, if you gave us a string here, well, in that case, we can say return, give me all the roles associated with the user, and just check to see if any of them have a name column equal to what is passed in.
say return, give me all the roles associated with the User, and just check to see if any of them have a name column equal to what is passed in. All right, does that make sense? We're just saying get the user's roles, and right now, they have none. We haven't done that part yet. But when we do, find all of the roles for the User, and tell me if any of them have a name column equal to, in this case, manager. And if so, yes, they have that role. But now, what if you didn't give me a string? You gave me a collection instead?
But now, what if you didn't give me a string? You gave me a Collection instead? Well, in that case, yeah, well, you could do if you want. So I'll show you a better way. But if you wanted to, you could do a forEach, and then call this method recursively. So you could say forEach role as r, then you could say if $this, and call the method recursively, and pass through just the name of the role itself. That would work. And then you can return true if it has the role, otherwise return false. So yeah, that, in fact, would do the trick.
And then you can return true if it has the role, otherwise return false. So yeah, that, in fact, would do the trick. Or maybe we can just use a simple intersection, because that's really all we're trying to do here. So in that case, we could say, well, I have this role collection. So just tell me if you have any intersection with the list of roles that are associated with the user. Now, if that's confusing, just think of it this way. The intersect method, well, that allows us to remove any items from this role collection that are not present here.
The intersect method, well, that allows us to remove any items from this role collection that are not present here. So if you think about it, what that means is, well, if we perform this comparison and what we get back is an empty collection, that means, no, the user doesn't have the role. So we could do something like this, if you want. Now, if you prefer this one, some might say that's a little more readable. Fine, keep it. Otherwise, this should do the trick, I think. Why don't we test this out? We're going to go back to our routes file, and yeah, we can load the welcome view.
Test and refine authorization15:31
Why don't we test this out? We're going to go back to our routes file, and yeah, we can load the welcome view. That's fine. And let's see, let's just get rid of that entirely. And I will say, hello world. And now we'll do a couple checks here. Now we could do Gate::allows, but don't forget, in Blade, we can also use this can directive. So we will say, if you can edit the forum, let's assume we have a link to a forum, well, in that case, let's add a link here. Edit the forum.
in that case, let's add a link here. Edit the forum. Remember, this just simulates maybe you're editing a reply or deleting something. Just something to simulate that you have permission to edit the forum. All right, so if I were to view this in Chrome, and of course they don't have permission, they're not signed in. So let's do that. Let's go back here, simulate a login user, auth login using ID. And if I switch back to Chrome, they still should not have access, and they don't. And let's see, we have undefined variable permission on line 32.
And if I switch back to Chrome, they still should not have access, and they don't. And let's see, we have undefined variable permission on line 32. Ah, yeah, right here, we're in a closure. We have to pass that through. Sorry about that. Let's come back, give that a refresh. And I referenced intersection, of course. Why didn't you guys tell me? intersect. Okay, stupid mistakes, but I think this should get us back.
Intersect. Okay, stupid mistakes, but I think this should get us back. And it does. Now we don't have permission to edit the forum, so we don't see the link. Now let's give the User permission. php artisan tinker, namespace App, we will find the User. And don't forget, right now the User has no roles. Okay, I want to assign the role. So let's say role, and let's just find that first one, since that's all we have. I want to assign the role of manager to Johnny.
So let's say role, and let's just find that first one, since that's all we have. I want to assign the role of manager to Johnny. Okay, well, we don't have a helper method yet, like addRole, or we talked about saying actAs, we don't have any of that yet. So we'll just say manually, save this role. There we go. So we should see right here, yep, we have a link between this User and the role. That's the hook that we need. Now, don't forget, for the manager role, we've said that the role with an ID of one has permission to do whatever the permission with an ID of one is,
Now, don't forget, for the manager role, we've said that the role with an ID of 1 has permission to do whatever the permission with an ID of 1 is, in this case, edit forum. And in our Blade view, we asked, well, if the User can edit the forum, then show the link. Now, they can. So we should see it show up, but no, we don't see it. So let's do some debugging in our AuthServiceProvider. It must be here. We grab our permissions, we define a new permission, and
It must be here. We grab our permissions, we define a new permission, and then we check, and we forgot to return it. So, of course, we need to determine and return whether or not the user has the given role or roles. Okay, so if I give that a refresh, awesome, now we can see it. But if we remove that privilege, roles detach the given role, well, now they don't have permission, so they don't see it. Okay, cool, but let's just bring that back. roles attach role, and well, let's do two things first.
Okay, cool, but let's just bring that back. Role attach role, and well, let's do two things first. In Sublime, I want to have a little better API for adding a role. So maybe I could say assignRole, that would be fine, or act as, like I said, whatever you want. Let's accept that. Now, in this case, we could accept a Role object, but again, I think it would be nice if I could just say assignRole of manager. So in that case, I won't expect that to be an object. It can just be a string, and then we can fetch it manually.
So in that case, I won't expect that to be an object. It can just be a string, and then we can fetch it manually. For example, this roles save, and then let's just track that down. So we could say, Role::where('name', '=', $name)->firstOrFail(); And again, if you need to, you can make that sync. Okay, so yeah, I think that should do the trick. Why don't we assign this User another Role? Because right now, yes, they have permission to edit the forum, but no, they don't have permission to view reports or something like that.
And in fact, if I switch to SQL Pro, we don't even have a permission to manage the money. So let's do that now. Refresh tinker, set our namespace, and I want a new permission to manage the money. Okay, we'll say the name will be manageMoney, and the label will be manageBusinessFunds, and let's save that. Okay, but right now, the role of manager doesn't yet have permission to do this. So let's find the role, role, where the name is manager, and grab that first one. Okay, and if we say role.permissions, right now, yeah, they can only edit the forum.
Okay, and if we say role permissions, right now, yeah, they can only edit the forum. Let's give them a new one. Role, give permission to manage the money. Okay, good. So now if I say permissions, or let's get a fresh instance, by the way, since it has already been cached. Okay, now this role of manager can do these two things here. Now, what's cool about this is, because the user has already been assigned the role of manager,
Now, what's cool about this is, because the User has already been assigned the role of manager, well, they instantly inherited access to manage the money. So if I refresh this, we should see the link, and we do. So why don't we finish up by doing just a bit of cleanup, and we'll call it a day. First, we have a bunch of stuff related to roles and potentially more in the future as you build upon this code. So instead, why don't we reference a trait here? For example, hasRoles, I don't know, naming things is hard, but that'll do fine. So the namespace will be app, and we have a trait called hasRoles.
For example, hasRoles, I don't know, naming things is hard, but that'll do fine. So the namespace will be app, and we have a trade called hasRoles. And now anything related to roles for a User will be stored here. Okay, so now, well, how about we build up another User? We'll create a factory here and persist it. Cool, User with an ID of two. Okay, well, let's sign that person in or simulate that they are signed in. And if they will load the page, yeah, we don't see anything here. Okay, well, this person isn't the administrator. We just wanna give them permission to, for example, edit the form, and that's it.
Okay, well, this person isn't the administrator. We just wanna give them permission to, for example, edit the form, and that's it. So it sounds like I need a new Role entirely. I could add it directly here, or why don't we do it like this? role equals new Role(). This name will be the editor. And I'm sorry, let's make that lowercase. Okay, so the label will be site editor, and we'll save that. And now, we will find that user. So I think it's the most recent one, yeah.
And now, we will find that user. So I think it's the most recent one, yeah. So now, let's assign the role of editor to Erlang, which means Erlang now has access to any permissions associated with the role. But the only thing is, right now, if I refresh, there's the role, but we have no permissions associated with the role just yet. Let's do that now. We'll say, do we have a role? Yeah, role givePermissionTo, and I don't think I have a permission object. So let's just grab the first one, there we go.
