Currently, our project has a users.role_id
column. But what if a user can have many roles instead of just one?
In this lesson, let's change a one-to-many to a many-to-many relationship.
Migration: From One Foreign Key to Pivot Table
The obvious first step is to change the migration from users.role_id
to a pivot table called role_user
.
Migration
Schema::table('users', function (Blueprint $table) { $table->foreignId('role_id')->default(1)->constrained();}); Schema::create('role_user', static function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained(); $table->foreignId('role_id')->constrained();});
Notice: I added the id()
column as an auto-increment, but it's optional and just a personal preference.
User Model Relationship: From BelongsTo to BelongsToMany
We also changed the relationship in the Model, changing the method name from role()
to roles()
and removing the old fillable field.
use Illuminate\Database\Eloquent\Relations\BelongsTo;use Illuminate\Database\Eloquent\Relations\BelongsToMany; // ... protected $fillable = [ 'name', 'email', 'password', 'role_id', ]; // ... protected $with = [ 'roles']; public function role(): BelongsTo{ return $this->belongsTo(Role::class);}public function roles(): BelongsToMany{ return $this->belongsToMany(Role::class);}
Notice: I also added a $with
array, so user would be always loaded with their roles as a relationship. This is a bit "risky" and may cause unnecessary loading if you want to have just Users on other pages of your application. So, use it with caution.
Policy: Adding Collections
Did you see that $with
from the above? It allows us to have a $user->roles
Collection in multiple Policy methods without running into the N+1 Query problem.
From there, we can use a Collection method contains()
to check for the roles we want.
Next, we have to modify our Policy conditions to use...