Create Global Query With Laravel Global Scope
Tags: php, laravel, eloquentorm, backend
Published: 2023-01-20 01:25:27

---
title: Create Global Query With Laravel Global Scope
published: true
date: 2022-12-28 14:36:16 UTC
tags: 
- PHP
- Laravel
- EloquentORM
- Backend
canonical_url: https://fajarwz.com/blog/create-global-query-with-laravel-global-scope/
---

![Global illustration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q4jq3e8eb86sxc7yoh4h.png)

In backend development, sometimes we want the data to be retrieved with default query. For example, when we want to get all users data, we want our app retrieve only active users by default. All queries like, getting users only from Indonesia, getting users only above 18, getting users only female, we want all of it -- by default -- retrieve only active users, excluding inactive users (because we have two type of users, active and inactive).

In Laravel there is a feature called query scope. Query scope have two types, Local Scope and Global Scope. I already covered Laravel Local Scopes [here](https://fajarwz.com/blog/create-reusable-query-with-laravel-query-scope/), so this time I just want to share about Laravel Global Scopes.

One example of Global Scope in Laravel can be seen in Laravel's [Soft Delete](https://laravel.com/docs/9.x/eloquent#soft-deleting). When we use soft delete in Laravel, Laravel automatically retrieve only non-soft deleted data unless we chain our query with `withTrashed()` function. 

This is indeed very useful and Laravel let us create our own global scope for us to use in our app. So let's get started to create our own global scopes.

Global scope implemented the same from Laravel version 7.x to 9.x. 

## Example Case Intro
For example we want to create a Global Scope to make our post data query published posts by default. So we need a table named `posts` that contains `title`, `content`, and `is_draft` columns.

Here we will use Laravel 9.x. So first create an app with Laravel 9.x and create a database for it.

## Tutorial
### Creating Migration
Let's create the migration, we can run the following artisan command:

```
php artisan make:migration "create posts table"
```

After that write the content of the migration like this:

```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.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('content');
            $table->tinyInteger('is_draft')->unsigned(); // 1 for draft and 0 for published
            $table->timestamps();
        });
    }

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

```

### Initialize Post Model
Now create a `Post` model with the `$fillable` data. We can create the model with the following artisan command

```
php artisan make:model Post
```

After that a model named `Post` created in `app\Models\Post.php`. Fill the necessary field for the fillable.

```php
<?php

namespace App\Models;

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

class Post extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'content',
        'is_draft',
    ];
}
```

> **Read also:**
> * [How to Fix 'Client Intended to Send Too Large Body' Nginx 413 Error](https://fajarwz.com/blog/how-to-fix-client-intended-to-send-too-large-body-nginx-413-error/)
> * [Web Rendering: What Is SSR, CSR, SSG, and ISR?](https://fajarwz.com/blog/web-rendering-what-is-ssr-csr-ssg-and-isr/)
> * [Create Login With Google OAuth Using Laravel Socialite](https://fajarwz.com/blog/create-login-with-google-oauth-using-laravel-socialite/)

### Creating Database Seeder
We now will create a seeder for `posts` table. I will create just 3 records but it is up to you to add as much as you want. The seeder contain 2 published posts and 1 draft post.

Here I will just create the seeder inside `DatabaseSeeder.php`. Of course you can create it in a dedicated seeder file if you want.

```php
<?php

namespace Database\Seeders;

// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Post;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        Post::create([
            'title' => 'Laravel CRUD Tutorial',
            'content' => 'This is the content of Laravel CRUD Tutorial',
            'is_draft' => 0,
        ]);
        Post::create([
            'title' => 'VueJS CRUD Tutorial',
            'content' => 'This is the content of VueJS CRUD Tutorial',
            'is_draft' => 0,
        ]);
        Post::create([
            'title' => 'React CRUD Tutorial',
            'content' => 'This is the content of React CRUD Tutorial',
            'is_draft' => 1,
        ]);
    }
}

```

### Run The Migration and The Seeder
We can run them with one following command:

```
php artisan migrate --seed
```

Now we should see our `posts` table in the database exists with the dummy data.

### Creating Global Scopes
In Laravel 9.x we can initialize the Scope class with the following artisan command:

```
php artisan make:scope PublishedScope
```

After the command successful, we will have a new file named `PublishedScope` in `App\Models\Scopes` folder.

The class implements `Illuminate\Database\Eloquent\Scope` interface and requires it to implements one method named `apply`. There we can use our constraints we needed.

```php
// 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.
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        $builder->where('is_draft', 0);
    }
}
```

### Applying Global Scopes
To apply a global scope to a model, we need to override the model's `booted` method and use `addGlobalScope()` method. The `addGlobalScope` method accepts an argument: our scope instance.

We will update our `Post` model like so:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Models\Scopes\PublishedScope; // use the Scope

class Post extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'content',
        'is_draft',
    ];

    protected static function booted()
    {
        static::addGlobalScope(new PublishedScope); // assign the Scope here
    }
}

```

That's it, we successfully implemented our global scope.

## Test the Scope in Artisan Tinker
### Run a Query with Global Scope
We can always test it inside `php artisan tinker`. Here is how I test it inside `tinker`.

Everytime we run a query it will always implement our applied global scope unless we chain our query with `withoutGlobalScope()` or `withoutGlobalScopes()`.

When I run `\App\Models\Post::all()` tinker with return the following response:

![Testing in Tinker](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iu3nyergh4pey5dnyo1y.png)

It shows only the published or `is_draft = 0` data.

### Run a Query without Global Scope
And yes we can run our query without the model's global scope, we can use `withoutGlobalScope(MyScope::class)` to remove only a scope and `withoutGlobalScopes()` (notice the s postfix) to remove all scopes or `withoutGlobalScopes([FirstScope::class, SecondScope::class])` to remove multiple global scopes.

![Testing in Tinker without Global Scope](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qovkl15frofihyeo3a89.png)

> **Read also:**
> * [Laravel Api Documentation Generator With OpenAPI/Swagger Using DarkaOnLine/L5-Swagger](https://fajarwz.com/blog/laravel-api-documentation-generator-with-open-api-swagger-using-darkaonline-l5-swagger/)
> * [Laravel Rest API Authentication Using JWT Tutorial](https://fajarwz.com/blog/laravel-rest-api-authentication-using-jwt-tutorial/)

## Test the Scope in Web
I will implement the scope also to the web so I write the `web.php` like so:

```php
<?php

use Illuminate\Support\Facades\Route;
use App\Models\Post;
use App\Models\Scopes\PublishedScope;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    $posts = Post::all();
    $allposts = Post::withoutGlobalScope(PublishedScope::class)->get();

    return view('welcome', [
        'posts' => $posts,
        'allposts' => $allposts,
    ]);
});
```

And I modify the `welcome.blade.php` like so:

```php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Laravel</title>

        <!-- Fonts -->
        <link href="https://fonts.bunny.net/css2?family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">

        <!-- Styles -->
        <style>
            /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}a{background-color:transparent}[hidden]{display:none}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}a{color:inherit;text-decoration:inherit}svg,video{display:block;vertical-align:middle}video{max-width:100%;height:auto}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity))}.border-t{border-top-width:1px}.flex{display:flex}.grid{display:grid}.hidden{display:none}.items-center{align-items:center}.justify-center{justify-content:center}.font-semibold{font-weight:600}.h-5{height:1.25rem}.h-8{height:2rem}.h-16{height:4rem}.text-sm{font-size:.875rem}.text-lg{font-size:1.125rem}.leading-7{line-height:1.75rem}.mx-auto{margin-left:auto;margin-right:auto}.ml-1{margin-left:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.ml-2{margin-left:.5rem}.mt-4{margin-top:1rem}.ml-4{margin-left:1rem}.mt-8{margin-top:2rem}.ml-12{margin-left:3rem}.-mt-px{margin-top:-1px}.max-w-6xl{max-width:72rem}.min-h-screen{min-height:100vh}.overflow-hidden{overflow:hidden}.p-6{padding:1.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.pt-8{padding-top:2rem}.fixed{position:fixed}.relative{position:relative}.top-0{top:0}.right-0{right:0}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.text-center{text-align:center}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity))}.underline{text-decoration:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.w-5{width:1.25rem}.w-8{width:2rem}.w-auto{width:auto}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}@media (min-width:640px){.sm\:rounded-lg{border-radius:.5rem}.sm\:block{display:block}.sm\:items-center{align-items:center}.sm\:justify-start{justify-content:flex-start}.sm\:justify-between{justify-content:space-between}.sm\:h-20{height:5rem}.sm\:ml-0{margin-left:0}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:pt-0{padding-top:0}.sm\:text-left{text-align:left}.sm\:text-right{text-align:right}}@media (min-width:768px){.md\:border-t-0{border-top-width:0}.md\:border-l{border-left-width:1px}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:1024px){.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media (prefers-color-scheme:dark){.dark\:bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity))}.dark\:bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity))}.dark\:border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity))}.dark\:text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.dark\:text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity))}.dark\:text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity))}}
        </style>

        <style>
            body {
                font-family: 'Nunito', sans-serif;
            }
        </style>
    </head>
    <body class="text-center">
        <h1>Published</h1>
        @foreach ($posts as $post)
            <h2>{{ $post->title }}</h2>
            <p>{{ $post->content }}</p>
            <p>{{ $post->is_draft ? 'DRAFT' : 'PUBLISHED' }}</p>
        @endforeach

        <br>

        <h1>All Posts</h1>
        @foreach ($allposts as $post)
            <h2>{{ $post->title }}</h2>
            <p>{{ $post->content }}</p>
            <p>{{ $post->is_draft ? 'DRAFT' : 'PUBLISHED' }}</p>
        @endforeach
    </body>
</html>
```

And after we run the `php artisan serve` we will get the following result

![Test in web](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wxob98ad0lqrrek9260y.png)

## Conclusions
And that's it, we have successfully implemented our own global scope and tested it. Now we know how to create and apply a global scope, run a query with the global scope and run a query without it.

A repo for this example case can be found here [fajarwz/blog-laravel-global-scope](https://github.com/fajarwz/blog-laravel-global-scope).

## Reference
[Eloquent: Getting Started - Laravel Scopes - Laravel](https://laravel.com/docs/9.x/eloquent#global-scopes)

## Read Also
[Create Reusable Query With Laravel Query Scope](https://fajarwz.com/blog/create-reusable-query-with-laravel-query-scope/)