As I continue learning Laravel, often I find the need to create basic features for my sample applications. A common feature is the ability to assign users to a role. While role management packages for this feature already exist, I decided to build one myself and use it as a learning experience.
I am going to assume you already have Laravel 5.4 or greater installed along with Laravel Authentication setup and a semi-working knowledge of Laravel.
What needs to be done?
We need the ability to determine if a user is in a role. A user can have many roles and a role can have many users; also known as a many-to-many relationship. Laravel provides the tools and documentation to easily create a many-to-many relationship between models.
In Laravel a many-to-many relation can be created between two models by referencing each other through belongsToMany()
relations and a pivot table which I will explain below.
I will not be covering the user interface to manage roles with this tutorial. The users and roles can easily be managed via Tinker for the scope of this tutorial.
Creating the Role model (and migration)
Let’s create the Role model along with a migration.
$ php artisan make:model Role --migration
Open up the migration {timestamp}_create_roles_table.php
file within the database/migrations
directory. Update the up()
method with the code below.
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateRolesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('roles', function (Blueprint $table) {
$table->increments('id');
$table->string('name')->unique();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('roles');
}
}
The schema for a Role simple; An ID, unique name, and time stamps. Now run the migration.
$ php artisan migrate
Update the Role model to create a relation with the User model. Open the app/Role.php
model file and add the following.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['name'];
/**
* App\User relationship
*
* @return Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function users()
{
return $this->belongsToMany(User::class);
}
}
Add name
field to the $fillable
array variable or else name
values will not be saved to the role. The users()
method is a relation definition that will return a BelongsToMany
object.
Open Tinker to save a role to the database.
$ php artisan tinker
$ App\Role::create(['name' => 'admin']);
Ensure the role was created by displaying all roles.
$ App\Role::all();
Adding a Role relationship to the User
Now that our Role model has a relationship to the User model, adjust the User model to reference the Role model.
Open the app/User.php
model and add the roles()
method like below.
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* App\Role relation.
*
* @return Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function roles()
{
return $this->belongsToMany(Role::class);
}
}
The User model now has a relation to the Role model. Since the relation is going to be many-to-many, a pivot table will be needed to store the relation.
Creating a pivot table
Whenever a many-to-many relationship is needed between two database tables, an intermediate table known as a “pivot table” will be needed to manage the relation. Create a migration script to create the pivot table.
$ php artisan make:migration create_role_user_pivot_table --create=role_user
Please note that Laravel uses a naming convention when referencing relations between models. The naming convention is alphabetical order, singular, and separated by an underscore; In this case role_user
will let Laravel know the table is a pivot for the role
and user
table. The role_id
column on role_user
will reference id
on the role
table. More information can be found in the Defining Relationships section of the Laravel documentation.
Open up the new migration file within the database/migrations/{timestamp}_create_role_user_table.php
and modify the up()
method like the following.
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateRoleUserTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('role_user', function (Blueprint $table) {
$table->integer('role_id');
$table->integer('user_id');
$table->primary(['role_id', 'user_id']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('role_user');
}
}
Simple enough, right? The role_id
and user_id
columns were both set to primary which will prevent a duplicate relation from being defined.
With the pivot table migration defined, run the migration.
$ php artisan migrate
Adding a user to a role
Now that our model relations and pivot table is setup, open Tinker and test it out.
$ php artisan tinker
Within Tinker, grab some data as variables.
$ $user = factory(App\User)->create();
$ $role = App\Role::first();
Create the association with the attach()
method.
$ $user->roles()->attach($role);
Dump $user->roles
to see the relation to the user.
$ $user->roles
You can find more information for adding and removing relationships within the Inserting & Updating Related Models section of the Laravel documentation.
A lot more could be done with the User to Role relation; Adding helper methods to the user and creating a middleware to protect actions by a user’s role are two that come to mind. I will save both of those for the next post.