Use Laravel Observers and Global Scopes to Create User Multi-Tenancy

Multi-tenant application are pretty common these days, where database are entries can be accessed only by users who created them. In this article, I will show the simplest way to achieve it in Laravel.

First, I want to admit that there are a lot of ways to implement or even think about multi-tenancy. For more thoughts on this, I recommend a wonderful presentation called Multi-Tenant Laravel by Tom Schlick. In this article, I will show multi-tenancy based on single database and Laravel Global Scopes.

Now, let’s add multi-tenancy to our database entries. Let’s imagine we have DB table called posts, and we want each user to see only their own posts.

Step 1. User’s field in DB table

First, we need to add user’s field into posts table.

php artisan make:migration add_user_id_to_posts_table

Then, we add a foreign key to users table:

$table->unsignedInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');

Finally, this field should be in model’s fillable array:

class Post extends Model
{
    protected $fillable = ['name', /* ... more fields ... */ 'user_id'];
}

Step 2. Fill in user_id automatically

Now, we need to fill it in automatically with currently logged in user, right? For that, we can use Laravel Observers.

Generate observer class:

php artisan make:observer PostObserver --model=Post

Inside of it, we add creating() method:

class PostObserver
{
    /**
     * Handle to the post "creating" event.
     *
     * @param  \App\Post  $post
     * @return void
     */
    public function creating(Post $post)
    {
        $post->user_id = auth()->id();
    }
}

And we register the observer in app/Providers/AppServiceProvider.php:

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Post::observe(PostObserver::class);
    }
}

Step 3. Filter by User: Access only your own posts

Final goal is to allow access only to posts that you created yourself. To do that, we could add something like ->where(‘user_id’, auth()->id()) in all Eloquent queries, but there’s a better way – to use Eloquent Global Scopes.

We create a file app/Scopes/PostUserScope.php:

namespace App\Scopes;

use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class PostUserScope implements Scope
{
    /**
     * Apply the scope to a given Eloquent query builder.
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        if (auth()->check()) {
            $builder->where('user_id', auth()->id());
        }
    }
}

And we attach it to app/Post.php model by loading in in model’s boot() method:

namespace App;

use App\Scopes\PostUserScope;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{

    protected $fillable = ['name', 'user_id'];

    /**
     * The "booting" method of the model.
     *
     * @return void
     */
    protected static function boot()
    {
        parent::boot();

        static::addGlobalScope(new PostUserScope);
    }

}

And that’s it! From now, all the queries for Post model will be filtered by logged in user. So we’ve achieved our goal, to build a basic multi-tenancy system, where every user can create/manage only their own data.

Like our articles?
Check out our Laravel online courses!

4 COMMENTS

  1. Hi Povilas,
    I have been reading your Laraveldaily blog for a while now. I think you guys are doing a great job!
    I have a suggestion: maybe instead of using laravel observers (model events) it makes more sense to use a posts relationship when creating a post, because it reads better.

    auth()->user()->posts()->create([‘body’ => ‘hello world]);

LEAVE A REPLY

Please enter your comment!
Please enter your name here