Applying common constraints with global scopes in Laravel

Laravel devs, here's a gem for you: πŸ’Ž

Use global scopes to apply common constraints to your queries. Global scopes allow you to encapsulate query logic that applies to all queries for a given model, making your query logic reusable and maintainable. In this blog post, we'll explore how to use global scopes and provide a real-life example to demonstrate their benefits.

Why Use Global Scopes?

  • Consistency: Apply consistent query constraints across all queries for a model.
  • Reusability: Encapsulate commonly used query logic for reuse across multiple queries.
  • Maintainability: Centralize query logic, making it easier to maintain and update.

Step-by-Step Implementation

Let's walk through the process of setting up and using global scopes in a Laravel application.

Step 1: Setting Up the Model

Ensure you have a model to work with. In this example, we'll use a Post model and create a global scope to only show published posts.

// app/Models/Post.php

namespace App\Models;

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

class Post extends Model
{
    // Boot method to add the global scope
    protected static function boot()
    {
        parent::boot();

        static::addGlobalScope('published', function (Builder $builder) {
            $builder->where('is_published', true);
        });
    }
}

Step 2: Creating the Controller Method

Create a controller method that retrieves posts. The global scope will automatically filter the posts to only show published ones.

// app/Http/Controllers/PostController.php

namespace App\Http\Controllers;

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

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

        return view('posts.index', ['posts' => $posts]);
    }
}

Step 3: Setting Up the Route

Define a route that points to the controller method.

// routes/web.php

use App\Http\Controllers\PostController;

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

Step 4: Creating the View

Create a view to display the posts.

<!-- resources/views/posts/index.blade.php -->

<!DOCTYPE html>
<html>
<head>
    <title>Posts</title>
</head>
<body>
    <h1>Published Posts</h1>
    <ul>
        @foreach ($posts as $post)
            <li>{{ $post->title }}</li>
        @endforeach
    </ul>
</body>
</html>

Example: Filtering Published Posts

In a real-life scenario, you might need to ensure that only published posts are shown to users. Using a global scope, you can encapsulate this logic and apply it consistently across all queries for the Post model.

Creating Dummy Data

Let's generate some dummy data to work with.

php artisan tinker

// Inside Tinker
Post::factory()->create(['title' => 'Published Post 2', 'is_published' => true]);
Post::factory()->create(['title' => 'Published Post 3', 'is_published' => true]);

This command will generate some published and unpublished posts in your database.

Viewing the Published Posts

Access the /posts route in your browser to see the list of published posts.

Advanced Example: Combining Multiple Scopes

You can also combine multiple global scopes for more complex filtering logic. For example, if you want to filter posts by both published status and a specific user, you can define another scope.

// app/Models/Post.php

namespace App\Models;

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

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

        // Scope to filter by published status
        static::addGlobalScope('published', function (Builder $builder) {
            $builder->where('is_published', true);
        });

        // Scope to filter by user
        static::addGlobalScope('byUser', function (Builder $builder) {
            $builder->where('user_id', auth()->id());
        });
    }
}

In this example, the Post model will only return posts that are both published and belong to the authenticated user.

Conclusion

Using global scopes in Laravel allows you to apply common query constraints consistently across all queries for a given model. This approach improves the reusability, maintainability, and readability of your code by centralizing query logic. By following the steps outlined in this blog post, you can leverage the power of global scopes to ensure uniform data access rules and simplify your query logic.

Found this helpful?

If this guide was helpful to you, subscribe to my daily newsletter and give me a follow on X/Twitter. It helps a lot!

Subscribe to Harris Raftopoulos

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe