Now, let's take care of the situation of multiple tenants per user.
We have the database structure already, with tenant_user
pivot table. Now, the question is where to save the active current tenant.
Two choices that I would suggest:
is_active
in the pivot.users
table.In this lesson, we will use the second method and add the current_tenant_id
column to the users
table.
First, the migration.
php artisan make:migration "add current tenant id to users table"
database/migrations/xxx_add_current_tenant_id_to_users_table.php:
Schema::table('users', function (Blueprint $table) { $table->foreignId('current_tenant_id')->nullable()->constrained('tenants');});
app/Models/User.php:
class User extends Authenticatable{ protected $fillable = [ 'name', 'email', 'password', 'current_tenant_id', ]; // ...}
Let's refresh the database so we can make sure that everything works.
php artisan migrate:fresh
Then, we need to set the active tenant when a user registers.
app/Http/Controllers/Auth/RegisteredUserController.php:
class RegisteredUserController extends Controller{ // ... public function store(Request $request): RedirectResponse { $request->validate([ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class], 'password' => ['required', 'confirmed', Rules\Password::defaults()], ]); $user = User::create([ 'name' => $request->name, 'email' => $request->email, 'password' => Hash::make($request->password), ]); $tenant = Tenant::create(['name' => $request->name . ' Team']); $tenant->users()->attach($user); $user->update(['current_tenant_id' => $tenant]); event(new Registered($user)); Auth::login($user); return redirect(route('dashboard', absolute: false)); }}
After registering in the database, we see that the current_tenant_id
is set correctly.
Now, let's register with a new user and manually add the second user to the first tenant.
Next, let's add the ability to switch between tenants. We will add the switch above the logout button in the navigation.
resourves/views/layouts/navigation.blade.php:
// ... <x-slot name="content"> <x-dropdown-link :href="route('profile.edit')"> {{ __('Profile') }} </x-dropdown-link> @if (auth()->user()->tenants()->count() > 1) @foreach (auth()->user()->tenants as $tenant) <x-dropdown-link :href="route('tenants.change', $tenant->id)" @class(['font-bold' => auth()->user()->current_tenant_id == $tenant->id])> {{ $tenant->name }} </x-dropdown-link> @endforeach @endif <!-- Authentication --> <form method="POST" action="{{ route('logout') }}"> @csrf <x-dropdown-link :href="route('logout')" onclick="event.preventDefault(); this.closest('form').submit();"> {{ __('Log Out') }} </x-dropdown-link> </form></x-slot> // ...
Next, let's create a Route with the name tenants.change
and a Controller for it.
php artisan make:controller TenantController --invokable
route/web.php:
Route::get('/', function () { return view('welcome');}); Route::get('/dashboard', function () { return view('dashboard');})->middleware(['auth', 'verified'])->name('dashboard'); Route::middleware('auth')->group(function () { Route::get('tenants/change/{tenantId}', \App\Http\Controllers\TenantController::class)->name('tenants.change'); Route::resource('tasks', \App\Http\Controllers\TaskController::class); Route::resource('projects', \App\Http\Controllers\ProjectController::class); Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');}); require __DIR__.'/auth.php';
In the Controller, we must do three things:
app/Http/Controllers/TenantController.php:
class TenantController extends Controller{ public function __invoke($tenantId) { // Check tenant $tenant = auth()->user()->tenants()->findOrFail($tenantId); // Change tenant auth()->user()->update(['current_tenant_id' => $tenant->id]); // Redirect to dashboard return redirect()->route('dashboard'); }}
In the navigation dropdown, we can see a list of tenants the user has:
The tenant in bold is active. After selecting another tenant, it would seem nothing happened, but the tenant should be changed.
Lastly, we must change the FilterByTenant
trait to how we get the current tenant ID.
app/Traits/FilterByTenant.php:
trait FilterByTenant{ protected static function booted(): void { $currentTenantId = auth()->user()->tenants()->first()->id; $currentTenantId = auth()->user()->current_tenant_id; static::creating(function (Model $model) use ($currentTenantId) { $model->tenant_id = $currentTenantId; }); static::addGlobalScope(function (Builder $builder) use ($currentTenantId) { $builder->where('tenant_id', $currentTenantId); }); }}
If you create a project or task in one tenant and switch to another, you shouldn't see those records.
To complete our multi-tenancy system, we must take care of two things:
Follow the next lessons for those topics.