Courses

Laravel 12 Multi-Tenancy: All You Need To Know

Filter DB Records by Active Tenant

Summary of this lesson:
- Adding tenant_id to projects and tasks tables
- Creating FilterByTenant trait for automatic tenant assignment
- Implementing Global Scope for tenant-based filtering
- Adding BelongsToMany relationship between User and Tenant

Now, let's filter the records by my current team. For that, we will add tenant_id column to all the DB tables that should be "tenantable". We create a Trait, and this will be a lesson about Traits and Scopes, similar like we had in one earlier lesson.


So, let's generate two migrations to add tenant_id to projects and tasks tables.

php artisan make:migration "add tenant id to projects table"
php artisan make:migration "add tenant id to tasks table"

database/migrations/xxx_add_tenant_id_to_projects_table.php:

Schema::table('projects', function (Blueprint $table) {
$table->foreignId('tenant_id')->constrained();
});

database/migrations/xxx_add_tenant_id_to_tasks_table.php:

Schema::table('tasks', function (Blueprint $table) {
$table->foreignId('tenant_id')->constrained();
});

Now, let's create a trait, FilterByTenant.

php artisan make:trait Traits/FilterByTenant

We will use the same booted() method for the creating event and a global scope in the trait.

app/Traits/FilterByTenant.php:

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
 
trait FilterByTenant
{
protected static function booted(): void
{
$currentTenantId = auth()->user()->tenants()->first()->id;
 
static::creating(function (Model $model) use ($currentTenantId) {
$model->tenant_id = $currentTenantId;
});
 
static::addGlobalScope(function (Builder $builder) use ($currentTenantId) {
$builder->where('tenant_id', $currentTenantId);
});
}
}

We haven't added the tenant's relationship to the User Model.

app/Models/User.php:

use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 
class User extends Authenticatable
{
// ...
 
public function tenants(): BelongsToMany
{
return $this->belongsToMany(Tenant::class);
}
}

We must add this trait to the Project and Task Models.

app/Models/Project.php:

use App\Traits\FilterByTenant;
 
class Project extends Model
{
use FilterByTenant;
 
// ...
}

app/Models/Task.php:

use App\Traits\FilterByTenant;
 
class Task extends Model
{
use FilterByTenant;
 
// ...
}

Cool, so we're filtering the records by active tenant. Next, what if the user has multiple tenants? How to switch between them?

Previous: Create Tenant at Registration
avatar

I'm trying to test this using phpunit. But auth()->user() is null in the test even with $this->actingAs() or $this->be() Please let me know how to access the user instance.

avatar

Should be working, can't really help without debugging your code on why the user doesn't stay active.

avatar
You can use Markdown
avatar

Thanks for zooming in when typing small fonts. For instance, when making a trait class.

👍 6
avatar
You can use Markdown
avatar

Isn't $currentTenantId the first tenant of the user as a single user can belong to multiple tenants?

avatar

It is! But that was the point of this lesson. At the start of the next lesson, we move this variable to a different place to allow switching tenant ID :)

avatar

Ohhh...

I think I imagined it too earlier. :)

Thanks

avatar
You can use Markdown
avatar

I test my relationships on tinker. Adding this global scope, I'm unable to test them as it gives error due to user_id constraint on the models.

Is there a way to test relationships on tinker with this global scope applied or do I have to disable my trait each time I'm testing?

avatar
You can use Markdown
avatar
You can use Markdown