In the last post we installed Laravel-permission. This package provides us with database tables and an API to store and manage roles and permissions. Unfortunately, this still leaves us having to write a significant amount of code in order to assign users into roles and assign permissions to roles.
Rather than doing all this through code, it makes sense to create a simple user interface that will allow basic admin functionality in assigning users into roles and assigning permissions to those roles. In order to create the interface, we need to have a basic understanding of Spaties data model and how it manages roles and permissions.
This diagram describes Spatie's database model for roles and permissions.
Users can assume many different Roles and each role can have many different permissions. For example in the tennis club a member can be both an System Admin and a Club Secretary - or - a member and a Club Coach. Permissions are created by the Application Developer and might include, in the case of the tennisclub things like "Create New Member" or "Delete New Member". The names of the permissions have no special significance and a developer can give the permissions any name he or she likes. Ultimately the delveloper will associate a given permission with the ability to access a certain group of routes which allow Users to complete that particular task.
In most of the applications that I have ever developed I prefer to assign users into a single role. This keeps things much cleaner and I can easily control the menu/interface that users acting in that role might see. Larger systems may require this more sophisticated many to many view of the roles-users relationship. For now, I will describe how to build the UI based on this approach and later I will describe how to restrict this so that only one role can be assigned to any given user (even though the database allows for many). This will simplify the use of the laravel-permission system.
After the many to many relationships have been resolved the Entity Relationship Diagram looks like this
This now explains the four tables that were generated by running the database migration. The model_has_roles and the role_has_permissions tables are there only to maintain a cross reference of which users are assuming which roles and which roles have which permissions.
In order to create a user interface to the Spatie system we will use infyom to scaffold the Users table, the Roles table and the Permissions tables. We will not need most of the CRUD generated by this scaffold but it will allow us to quickly and easily create screens which will allow us to create new roles and permissions, assign users into roles and assign permissions to roles.
In reality Users are created by the users themselves registering in the system. This way they get to give their own email address and password.
Before we start scaffolding it's important to point out that Spatie has its own model classes for Roles, and Permissions and the User class already exists as app\User.php. In regenerating and effectively duplicating these classes we might be breaking a few rules but after the code has been scaffolded we can repurpose it to point at the Spatie classes.
To scaffold the Users table type
php artisan infyom:scaffold users --fromTable --tableName users
The purpose of scaffolding the users table is not to allow people to create and edit a user's details as this is generally done by the user themselves, but we can build on the scaffolded code to allow roles to be assigned to users. To do this we need to scaffold the roles table.
php artisan infyom:scaffold roles --fromTable --tableName roles
Before we can use these newly scaffolded classes we need to remove the softDeletes as these kind of user/permission admin classes shouldn't really have softDeletes. Edit both the app\Models\role.php and the app\Models\users.php classes and remove the lines related to softDeletes at the top.
Gaurds
Gaurds are laravel's way of describing the the different mechanisms which are used to secure different applications. The most common one, and the one most of us will be familiar with is the use of sessions and cookies. Laravel refers to this guard simply as 'web'. Programmers building a RESTFul API based application will need to use a mechanism other than Sessions to ensure their application is stateless. In this instance, the guard_name should be set to 'api'. For our purposes, we always want to use Sessions and Cookies so the gaurd name should always be set to 'web'. This is why, in the last section, we changed the migration file before we ran it to set the default value of the gaurd_name attribute to 'web'. Now we need only remove the guard_name field from the view and all roles will have the guard_name set to 'web' by default. We will have to do the same thing when we scaffold the permissions table.
To make sense of this change you should remove the following block (outlined in red) associated with the guard_name field altogether from resources\views\roles\show_fields.blade.php (the fields for the create view) and also from resources\views\roles\fields.php (the fields for the edit view)
Now if you go to create a new role using http://localhost/roles/create, you will only be asked to give the role name and the guard_name 'web' will be inserted automatically as the default value.
Next, we are going to add two Controller functions to the app/Http/usersController.php file which will allow us to (1) present a view to the system administrator which will allow them to assign users into roles (2) to take the submission of that view and process it in order to update the database.
First the assignRoles function
public function assignRoles($id) { $user = User::findOrFail($id); $roles = SpRole::all(); return view('users.assignroles') ->with('user', $user)->with('roles',$roles); }
This function finds the user with the id which has been passed to it as an argument. The function then uses the Spatie Roles class to find all Roles in the database. A user object and an array of objects containing all the Roles is then passed to a view in the users folders called assignroles.blade.php. When this view is submitted it will pass all the data from the form to the updateRoles function as follows:
public function updateRoles($id, Request $request) { $user = User::findOrFail($id);; $roles = SpRole::all(); foreach($roles as $role) { if (isset($request->role[$role->id])) { $user->assignRole($role); } else { $user->removeRole($role); } } Flash::success('Roles updated successfully.'); return redirect(route('roles.index')); }
This function finds a user object associated with the id that is passed to it. The function then retrieves all the Roles objects. The loop goes through all the Roles and checks to see if the checkbox for that roles has been checked. If it has been checked the user is assigned into that Role, if the check box is unchecked the role is removed from the user.
It doesn't really matter where, within the controller, you place these functions. Here they are in my usersController
These functions make use of t the User class that comes built-in to Laravel and the Spatie Roles class. To use these we must add use clauses at the top of the class as follows
use Spatie\Permission\Models\Role as SpRole; use App\Models\User as User;
These go at the top of the file underneath the other use clauses
Next, we need a view to present to the System Admin. The idea behind this view is that it will present the many-to-many relationship as a range of checkboxes so that every role that is to be associated with a user will be presented as a range of checkboxes. Where the user already occupies that role the checkbox will be presented as checked. The System Admin can add additional roles by checking the checkboxes as required.
To create this view save the following file into resources/views/users/assignroles.blade.php
@extends('layouts.app') @section('content') <style> .control-label { text-align: right; } </style> <section class="content-header"> <h1> Roles for User: <b>{{$user->name}}</b> </h1> </section> <div class="content"> <div class="box box-primary"> <div class="box-body"> {!! Form::model($user, ['route' => ['roles.rolesupdate', $user->id], 'method' => 'patch']) !!} <div class="row" style="padding-left: 20px"> <div class="form-group col-sm-4"> @foreach($roles as $role) <label class="control-label col-sm-10">{{$role->name}}</label> <div class="col-sm-2"><input class="checkbox-inline" type="checkbox" name="role[{{$role->id}}]" @if($user->hasRole($role)) checked @endif ></div> @endforeach </div> <!-- Submit Field --> <div class="form-group col-sm-12"> {!! Form::submit('Save', ['class' => 'btn btn-primary']) !!} <a href="{!! route('roles.index') !!}" class="btn btn-default">Cancel</a> </div> </div> {!! Form::close() !!} </div> </div> </div> @endsection
Finally, to enable all this we need to add routes to routes/web.php - add the following two lines to create the routes
Route::get('/users/assignroles/{id}', 'App\Http\Controllers\UsersController@assignRoles')->name('users.assignroles'); Route::patch('/users/updateroles/{id}', 'App\Http\Controllers\UsersController@updateRoles')->name("roles.rolesupdate");
Before you can try out your new UI to assign Users into different roles you will need to register at least one user in your system(if you haven't already) by visiting http://localhost:8000/register. You should also create a few roles(more than one) so you test that the system is working properly. You can create roles by using the standard routes generated by the scaffolder. Http://localhost:8000/roles/create. For the tennisclub example I created the roles of System Admin, Coach and Club Secretary. You can now try out your Spatie user interface. If you visit http://localhost:8000/users/assignroles/1 - this will assign roles to the user with id=1. In my case this produces the following.
In order to enable the many to many relationship between roles and users this screen uses checkboxes so that Users can be assigned into many Roles. In my view, this many to many association is overkill and creates more problems when it comes to things like using the role to control what UI or Menu is displayed. If a user belongs to multiple roles it's not clear which view should be presented to them. In a later post, I'll demonstrate how to turn these checkboxes into radio buttons so that users can only be assigned into one role. In the next post, we need to allow roles to be assigned multiple permissions.