Back to Course |
Laravel 11 Multi-Tenancy: All You Need To Know

Filter Multiple Models: With Traits or Relationships

Now, let's add the same user_id to another model: Tasks. We will use two approaches to filter the tasks.

First, as I mentioned, we have the project_id column. So we have project_id in the tasks table, but I also added the user_id.

database/migrations/xxx_add_user_id_to_tasks_table.php:

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

So, it's your choice whether to save user_id in all tables or to use a "higher" relationship with project_id: it will be slower to query but a smaller table without the user ID. We will see both approaches.


Option 1. Filter by User with Traits.

In the same way, as we did with the projects, we can add the anonymous global scope to the Task Model.

app/Models/Task.php:

use Illuminate\Database\Eloquent\Builder;
 
class Task extends Model
{
protected $fillable = [
'name',
'project_id',
];
 
protected static function booted(): void
{
static::creating(function (Task $task) {
$task->user_id = auth()->id();
});
 
static::addGlobalScope(function (Builder $builder) {
$builder->where('user_id', auth()->id());
});
}
 
// ...
}

We can check if everything works by creating a new task and checking the database.

If we register with a new user, we don't see any tasks or projects. Everything works. But, the problem now is the repeating code. We have the same booted() structure in two models.

So, we can move the booted() method to a trait.

php artisan make:trait Traits/FilterByUser

app/Traits/FilterByUser.php:

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

Then, we can remove booted() from the Models and use the Trait instead.

app/Models/Task.php:

use App\Traits\FilterByUser;
 
class Task extends Model
{
use FilterByUser;
 
protected $fillable = [
'name',
'project_id',
];
 
protected static function booted(): void
{
static::creating(function (Task $task) {
$task->user_id = auth()->id();
});
 
static::addGlobalScope(function (Builder $builder) {
$builder->where('user_id', auth()->id());
});
}
 
// ...
}

app/Models/Project.php:

use App\Traits\FilterByUser;
 
class Project extends Model
{
use FilterByUser;
 
protected $fillable = [
'name',
'user_id',
];
 
protected static function booted(): void
{
static::creating(function (Project $project) {
$project->user_id = auth()->id();
});
 
static::addGlobalScope(function (Builder $builder) {
$builder->where('user_id', auth()->id());
});
}
}

Everything works the same. Users can see only their own tasks and projects.

So, whenever you need to add a new Model and add the user_id filterable logic like this, you need to do two things:

  • Add the user_id column in Migrations
  • Use the FilterByUser Trait in the Model

Important notice: This filter works only on the Eloquent but not with DB::table() syntax. For example, if instead of Project::all() you use a Query Builder like DB::table('projects')->get(), this Trait will NOT work.

use Illuminate\Support\Facades\DB;
use App\Models\Project;
 
class ProjectController extends Controller
{
public function index()
{
$projects = Project::all();
$projects = DB::table('projects')->get();
 
return view('projects.index', compact('projects'));
}
}

The global scope won't work, and all the projects will be shown.


Option 2. Filter By project_id

Suppose you don't want to filter by user_id and you have another field to filter by, like project_id in the tasks table. Then, you wouldn't use a Trait because the code won't be repeated.

Again, we can use the booted() method on the Model but with a different filter.

app/Models/Task.php:

use Illuminate\Database\Eloquent\Builder;
 
class Task extends Model
{
use FilterByUser;
 
protected $fillable = [
'name',
'project_id',
];
 
protected static function booted(): void
{
static::addGlobalScope(function (Builder $builder) {
$builder->whereRelation('project','user_id', auth()->id());
});
}
 
public function project(): BelongsTo
{
return $this->belongsTo(Project::class);
}
}

After trying to add a project and task with a new registered user, only projects and tasks created by this user are seen. Everything works.


It's your personal preference how to filter. The second method query takes the relationship, so it is technically slower to select the data but faster to insert the data because it doesn't include user_id.

In short, that's all you need to know about a simple user-based multi-tenancy.

Now we move on to the next section of the course: team or company multi-tenancy. We will cover single and multiple database approaches with various packages.