Problem: Repeated DB role checks0:00
One of the issues with the role-based access system is that now we have to check if the User is in a given role. And that might not sound like a big deal, and really it's not. But behind the scenes, every time we check, we are hitting the database. And that's not very efficient. I mean, inside of a single request, we are hitting the database, what, three times? We, first of all, check the User's role with our role middleware whenever we set up the ArticleController routes. Then inside of the navigation view, if the User is in an admin, then we show the User's link. And then finally, inside of the index route for our articles, we check if the User is in the admin or the author roles. Now, I know that that doesn't sound like a big deal. But, you know, imagine a larger application with a lot more checks.
Solution: Load roles middleware0:49
Now, I know that that doesn't sound like a big deal. But, you know, imagine a larger application with a lot more checks. All of these checks are being done within a single request, which means that we are hitting the database multiple times within a single request. We need to eliminate that. And I have a solution. So we are going to make some middleware. And its sole purpose will be to load the roles into the context. So that now, whenever we check if a User is in a given role, we aren't going to be hitting the database. We will check the roles that we have stored in the context. This is going to be a lot more efficient.
Understanding request context1:23
We will check the roles that we have stored in the context. This is going to be a lot more efficient. And we haven't really lost anything as far as data integrity is concerned. Because, you know, what's going to happen within a single request? I mean, yes, you can make the argument that something will happen. But if something did happen anyway, things would be kind of messed up. So this is going to be a perfect solution. Now, if you're not familiar with the context, the idea is very simple. It is simply a session for a request. So we can load any data that we want into the context.
Implementing loadRoles logic1:55
It is simply a session for a request. So we can load any data that we want into the context. And we can use that data anywhere throughout our application within that request. But once the request is done, everything is forgotten. So then we have to reload all of that data back into the context. It sounds like a lot of work, but really it's not. So our loadRoles middleware. The first thing we are going to do is check if the User is authenticated. Because if they aren't, well, there's nothing that we need to do there. So if the User is authenticated, then great.
Because if they aren't, well, there's nothing that we need to do there. So if the User is authenticated, then great. We will then add data to the context. And we can do that with a couple of methods. The first method is add. And then we simply specify the key, which would be roles in this case. And then we would need to supply the roles that we want to store. It's not really an issue, but when you use the add method, then anything that you add to the context is going to be logged, which you might want.
then anything that you add to the context is going to be logged, which you might want. You might want that behavior. I don't necessarily want that in this case. So instead of add, I'm going to use the addHidden method. This still adds data to the context, but it is not logged. So that's why it's hidden. It's not really hidden. So our key is going to be roles. And then we want to load our roles, which we can get our user object.
So our key is going to be roles. And then we want to load our roles, which we can get our User object. We want the roles, but we also want this to be as simple as possible. Because whenever we check if a User is in a role, we are just checking the name of that role. We don't need the ID. We don't need anything else. So I'm going to pluck the names of those roles, and we are just going to work with an array of strings. We should also try to not necessarily emulate the behavior of a database.
and we are just going to work with an array of strings. We should also try to not necessarily emulate the behavior of a database. But one thing to consider is that databases are case insensitive, or at least they are by default. So if we wanted to check if a User is in an admin role, we could check using the string admin, but we could have mixed case for that string, and everything would work fine. So we need our data to be case insensitive. And really the only way that I know to do that is to normalize everything.
So we need our data to be case insensitive. And really the only way that I know to do that is to normalize everything to the same case. So what we will do is call array_map, and we will call strtolower, so that what gets stored in the context is going to be an array of our roles, and they will all be in lower case. But we also want this to be automated. We want this middleware to execute basically for every request. So we need to register this inside of our app.php file. But we don't want to just add it to the middleware,
Registering middleware in web group4:38
So we need to register this inside of our app.php file. But we don't want to just add it to the middleware, because our middleware now is going to rely upon several things. The User has to be authenticated, which means that we need session support and all of that wonderful stuff. So we are going to append to the web group, so that any request that is going to be handled by the web group is then going to use this middleware as well. So that was app/http, middleware, and then load roles to middleware. And so with that done, all we need to do is go to our User model,
Updating User role methods5:09
So that was app/http, middleware, and then load roles to middleware. And so with that done, all we need to do is go to our User model, and we need to modify our methods that we use to check if a User is in a role. So we'll start with hasRole. We will first of all check if the context has a hidden key. So we will call the hasHidden method, and we will pass in our key, roles. If we have our roles, then basically we just want to check if our role is in the array. But remember, we need to normalize this as lowercase. So let's call strtolower, so that we compare a lowercase string to the lowercase strings in our array, and that's going to be just fine.
So let's call stringToLower, so that we compare a lowercase string to the lowercase strings in our array, and that's going to be just fine. So that now, whenever we check if a User is in a Role, we first check the context. If we have something in the context, then we use the context. Otherwise, we fall back to the database. And we will essentially do the same thing for the hasAnyRole, but this is going to be a little bit different, because we get an array of roles passed to this method. So instead of calling inArray, we will call array_intersect. And this is essentially going to find the matches between the two arrays.
So instead of calling in array, we will call array_intersect. And this is essentially going to find the matches between the two arrays. But once again, the array that was passed to this method needs to be normalized as lowercase strings. So here we will call array_map. We want strtolower, so that we end up with an array with all lowercase. But array_intersect doesn't return a Boolean value. It returns an array that contains the matches that were found in both of those arrays. So then what we need to do is return, if our matches is not empty, then the user is in one of those roles.
So then what we need to do is return, if our $matches is not empty, then the $user is in one of those roles. But if $matches is empty, then the $user is not in any of those roles. So here, once again, we first of all check if we have $roles inside of the context. And if we do, we use that. Otherwise, we fall back to the database. And everything should work just fine. So inside of the browser, we see the $user's link up in the navigation. And we also see the create button from the articles index view. We are currently signed in as admin.
And we also see the create button from the article's index view. We are currently signed in as admin. And so therefore, yes, we should see those things, which means that everything is working as it should. But let's do some tests. And if we had written out some actual tests, this would be really easy to check. But since we don't, there's nothing like checking the actual application. So let's change some of these, so that now we do not see the user's link in the navigation because we removed admin from that check, and we also don't see the create button. So it looks like everything is working okay.
because we removed admin from that check, and we also don't see the create button. So it looks like everything is working okay. But let's also test mixed case roles. So here, let's just have admin with some wonky casing. Let's go back to the index view and add admin back there. And once again, we see the user's link in the navigation, meaning that we successfully checked that wonky cased role. And then, of course, we have the admin button visible as well. So the context is a very powerful feature. It was added a little bit after the launch of Laravel 11.
So the context is a very powerful feature. It was added a little bit after the launch of Laravel 11. And it is extremely useful. We're able to automatically load our roles into the context, so that every time we check if a User is in a given role, we now check that cached information in the context. And we don't have to worry about working with stale or fresh data because fresh data is always added with every request.
