In this tutorial, 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.
After the step-by-step guide, I’ll provide some more examples and answer some frequently asked questions.
Let’s get started!
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:
<?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:
<?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:
<?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:
<?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:
<?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:
<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 Laracoding</p>
</footer>
</div>
</body>
</html>
Step 10: Define Routes
Define routes in web.php
to handle displaying posts:
<?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:
<?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:
<?php
namespace App\Models\Traits;
use App\Models\Scopes\PublishedScope;
trait IsPublishedScope
{
public static function bootIsPublishedScope()
{
static::addGlobalScope(new PublishedScope);
}
}
<?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.
Moreover, we’ve explored several more examples where global scopes prove beneficial. Additionally, we’ve addressed frequently asked questions, from disabling global scopes to adding them by base models or traits.
Now go ahead and find creative ways to enhance your own applications using global scopes. Happy coding!
References
- Global Scopes (Laravel Documentation)
This entry is part 4 of 4 in the series Query Scopes in Laravel Eloquent
- How to Use Model Scopes With Laravel Eloquent
- Using a Model Scope With Parameters in Laravel Eloquent
- Using a Model Scope With a Relationship in Laravel
- Using Global Scope in Laravel (With Practical Examples)