Multi-Tenancy

This module allows you to restrict access to CRUD entries only to the users who actually created them.

Video Demo

If you want to see this module in action, here's a 5-minute video:

How Does It Work

There are two types of Multi-Tenancy - while installing the module, you can choose: - User Multi-Tenancy (every user sees only records they created) - Team Multi-Tenancy (every user sees all records created by any member of their team)

After installing the module, you will see a checkbox for each CRUD separately, whether to use this restriction setting.

First, let's talk about User Multi-Tenancy.

How Does The Result Look In QuickAdminPanel Code?

First, for every affected CRUD we add a field created_by_id with relationship to User model:

class Customer extends Model
{
    // ...

    public function created_by()
    {
        return $this->belongsTo(User::class, 'created_by_id');
    }
}

Next, we have a special Trait for the filter by user: app/Traits/MultiTenantModelTrait.php

trait MultiTenantModelTrait
{
    public static function bootMultiTenantModelTrait()
    {
        if (!app()->runningInConsole() && auth()->check()) {
            $isAdmin = auth()->user()->roles->contains(1);

            static::creating(function ($model) use ($isAdmin) {
                // Prevent admin from setting his own id - admin entries are global.

                // If required, remove the surrounding IF condition and admins will act as users
                if (!$isAdmin) {
                    $model->created_by_id = auth()->id();
                }
            });

            if (!$isAdmin) {
                static::addGlobalScope('created_by_id', function (Builder $builder) {
                    $builder->where('created_by_id', auth()->id())->orWhereNull('created_by_id');
                });
            }
        }
    }
}

The code here may look complicated, but the logic is simple - whether to add global scope or not.

Read more about Eloquent Query Scopes in official Laravel documentaiton here.

Finally, we use that Trait in the model, like app/Customer.php:

use App\Traits\MultiTenantModelTrait;

class Customer extends Model
{
    use SoftDeletes, MultiTenantModelTrait;

    // ...

Team Multi-Tenancy

For this setting, we generate separate CRUD called Teams where administrator can manage the teams. Also, every user may belong to multiple teams:

class User extends Authenticatable
{
    // ...
    public function team()
    {
        return $this->belongsTo(Team::class, 'team_id');
    }
}

And then the Trait app/Traits/MultiTenantModelTrait.php is expanded with logic about teams:

trait MultiTenantModelTrait
{
    public static function bootMultiTenantModelTrait()
    {
        if (!app()->runningInConsole() && auth()->check()) {
            $isAdmin = auth()->user()->roles->contains(1);
            static::creating(function ($model) use ($isAdmin) {
                if (!$isAdmin) {
                    $model->team_id = auth()->user()->team_id;
                }
            });
            if (!$isAdmin) {
                static::addGlobalScope('team_id', function (Builder $builder) {
                    $field = sprintf('%s.%s', $builder->getQuery()->from, 'team_id');

                    $builder->where($field, auth()->user()->team_id)->orWhereNull($field);
                });
            }
        }
    }
}

How To Customize The Module?

All the logic of that module is inside of app/Traits/MultiTenantModelTrait.php file. So whatever you want to customize, you should do it there.

For example, you may want to customize how administrator records are treated. By default, entries created by administrator role user are visible to everyone, because they are considered "system records". If you want to change that, you need to delete orWhereNull('created_by_id') condition in the Trait:

$builder->where('created_by_id', auth()->id())->orWhereNull('created_by_id');

Another customization example, in a blog article: Teams Multi-Tenancy: Add “Team Admin” to Manage Users

For more complex logic, you can copy-paste some logic and create another trait, and use your trait in some models you want.

Finally, if on some models or some queries you want to disable that filtering, there is a method called withoutGlobalScope():

User::withoutGlobalScopes()->get();

Last updated