How to Use Global Scope in Laravel

How to Use Global Scope in Laravel

In this tutorial i will show How to Use Global Scope in Laravel.  we’ll explore the power of Laravel’s global scope using a practical example. We will be implementing a global scope to ensure we only retrieve posts that are set to published.

How to Use Global Scope in Laravel

After the step-by-step guide, I’ll provide some more examples and answer some frequently asked questions. You Can Learn How to Encrypt and Decrypt Model Data Using Casts in Laravel

Let’s get started! How to Use Global Scope in Laravel

Step 1: Create a Laravel Project

Begin by creating a new Laravel project if you haven’t already. Open your terminal and run:

composer create-project laravel/laravel blogapp
cd blogapp

Step 2: Create the Model

Generate a model for the Post using the following Artisan command:

php artisan make:model Post

Step 3: Create the Migration(s)

Now let’s create a migration for the posts table using the following command:

php artisan make:migration create_posts_table

Add the necessary code to the migration file:

database/migrations/2023_11_19_172846_create_posts_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('content');
            $table->boolean('published')->default(false);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

Step 4: Run the Migration

Now run the migration to create the “posts” table:

php artisan migrate

Step 5: Define the Global Scope

To create a new scope use the following artisan command:

 php artisan make:scope PublishedScope

Now edit the scope code to define its query constraint that limits the posts to published only:

app/Models/Scopes/PublishedScope.php

<?php

namespace App\Models\Scopes;

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

class PublishedScope implements Scope
{
    /**
     * Apply the scope to a given Eloquent query builder.
     */
    public function apply(Builder $builder, Model $model): void
    {
        $builder->where('published', true);
    }
}

Step 6: Register the Global Scope

In the Post model, apply the global scope:

app/Models/Post.php

<?php

namespace App\Models;

use App\Models\Scopes\PublishedScope;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected static function boot()
    {
        parent::boot();

        static::addGlobalScope(new PublishedScope());
    }

    // Other Model code ...
}

Step 7: Create a Controller

Generate a controller for handling posts:

php artisan make:controller PostController

Add the following code:

app/Http/Controllers/PostController.php

<?php

namespace App\Http\Controllers;

use App\Models\Post;

class PostController extends Controller
{
    public function index(Request $request)
    {
        // Retrieve all posts
        $posts = Post::all();

        return view('posts.index', compact('posts'));
    }
}

Step 8: Seed the Database With Test Data

Create a seeder to populate the posts table:

php artisan make:seeder PostSeeder

Edit PostSeeder and add code to the run method to generate some test records:

database/seeders/PostSeeder.php

<?php

namespace Database\Seeders;

use App\Models\Post;
use Illuminate\Database\Seeder;

class PostSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        // Create a published post (published is `true`)
        Post::create([
            'title' => 'First Post',
            'content' => 'This is the content of the first post.',
            'published' => true,
        ]);

        // Create a draft post (published is `false`)
        Post::create([
            'title' => 'Second Post',
            'content' => 'This is the content of the second post.',
            'published' => false,
        ]);

        // Add more test data as needed...
    }
}

Run the seeder:

php artisan db:seed --class=PostSeeder

Step 9: Create a View to Display Posts

Create a Blade view to display the posts. Use the provided HTML and add the following code within the container:

views/posts/index.blade.php

<html>
<head>
    <!-- Include Bootstrap to make it the example look better -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
            integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
            crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
    <h1>Posts list</h1>

    <!-- Posts List -->
    @foreach($posts as $post)
        <div class="card mt-3">
            <div class="card-body">
                <h5 class="card-title">{{ $post->title }}</h5>
                <p class="card-text">{{ $post->content }}</p>
            </div>
        </div>
    @endforeach

    <!-- Footer -->
    <footer class="mt-5 text-center">
        <p>Created with ♥ by DevScriptSchool</p>
    </footer>
</div>
</body>
</html>

Step 10: Define Routes

Define routes in web.php to handle displaying posts:

routes/web.php

<?php

use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;

Route::get('/posts', [PostController::class, 'index']);

Step 11: Test the Application

Launch the application by running:

php artisan serve

Now navigate to http://127.0.0.1:8000/posts in your browser. You should see only the published posts displayed:

That’s it! We’ve successfully added global scopes to control which posts are retrieved. Read on for more practical examples where this technique is beneficial as well as some FAQs and their answers.

Further Practical Examples of Using Global Scopes

Building on the concepts introduced in this tutorial, you can easily create additional global scopes to suit specific requirements.

Example 1: ActiveScope to retrieve only active Users

The following example demonstrates using an ActiveScope to interact with only users marked as ‘active’:

class ActiveScope implements Scope
{
    public function apply(Builder $builder, Model $model)
    {
        $builder->where('active', true);
    }
}

class User extends Model
{
    protected static function boot()
    {
        parent::boot();

        static::addGlobalScope(new ActiveScope());
    }

    // Other model code...
}

Example 2: TenantScope for Subscription to ensure access by the associated tenant

This example illustrates a TenantScope that improves security by restricting access to Subscription data belonging only to the logged-in tenant:

class TenantScope implements Scope
{
    public function apply(Builder $builder, Model $model)
    {
        $tenantId = auth()->user()->tenant_id; // Assuming authentication

        $builder->where('tenant_id', $tenantId);
    }
}

class Subscription extends Model
{
    protected static function boot()
    {
        parent::boot();

        static::addGlobalScope(new TenantScope());
    }

    // Other model code...
}

Frequently Asked Questions

1. How Can I Disable a Global Scope?

To temporarily disable a global scope, use the withoutGlobalScope() method in your queries.

This is useful in scenarios such as an admin area where all posts need to be viewed, including unpublished ones:

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;
use App\Models\Scopes\PublishedScope;

class AdminPostController extends Controller
{
    public function index()
    {
        // Fetch all posts, including unpublished ones
        $posts = Post::withoutGlobalScope(PublishedScope::class)->get();

        // Rest of your index logic...
    }

    // Rest of your controller logic...
}

2. How to Add a Global Scope to Multiple Models?

One way to add a global scope to all models is by creating a base model that applies the global scope while any model that extends BaseModel will inherit that global scope:

app/Models/BaseModel.php

<?php

namespace App\Models;

use App\Models\Scopes\PublishedScope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

class BaseModel extends Model
{
    protected static function boot()
    {
        parent::boot();

        static::addGlobalScope(new PublishedScope());
    }
}
<?php

namespace App\Models;

class Post extends BaseModel
{
    // No code to add global scope since BaseModel does that

    // ... other code for Post Model
}

3. Can I Add a Global Scope to Multiple Models Using Traits?

Using a trait is another excellent way to add global scopes to multiple models without using a base model. Here’s an example:

app/Models/Traits/IsPublishedScope.php

<?php

namespace App\Models\Traits;

use App\Models\Scopes\PublishedScope;

trait IsPublishedScope
{
    public static function bootIsPublishedScope()
    {
        static::addGlobalScope(new PublishedScope);
    }
}

app/Models/Post.php

<?php

namespace App\Models;

use App\Models\Traits\IsPublishedScope;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use IsPublishedScope; // Add this to any Model which needs the global scope
}

Conclusion

Global model scopes in Laravel offer a way to apply constraints consistently to queries at a model level.

In this tutorial, we used an example of a simple blog application that uses a global scope to help guarantee that only published posts can be retrieved.

Leave a Reply