Laravel scopes
Tags: programming, laravel, php, webdev
Published: 2024-02-02 10:57:29

Since the announcement of the new structure; I am excited about Laravel again 😃. By new structure, I am referring to the debloating of the initial skeleton template you get when running "laravel new".

Although this article is not about Laravel 11 exactly, it just has excited me enough to write about Laravel again. I probably will cover all the new features in a later post.

For now, though, let's talk about Eloquent queries.

## Eloquent

If you have ever spent some quality time in Laravel, you should know Eloquent by now. It's easily one of the most flexible and clean database ORM's around.

Just a quick refresher on Eloquent:
```php
// Create a new post
Post::create(['title' => 'Laravel is Cool']);

// Find posts
$posts = Post::select("title", "slug")
    ->where('status', 'published')
    ->orderBy('title')
    ->take(10)
    ->get();

// Published posts where the post has a valid featured image.
$posts = Post::where('status', 'published')
    ->reject(function (Post $post) {
        return $post->featured_image == '';
    })
    ->get();

```

Pretty easy right? The chainable methods are very SQL-like, so if you are familiar with SQL, you will find adjusting to Eloquent much easier than most other ORMs.

## So, what are scopes?

When you look closely at the "reject" function above, you will notice this looks a bit "weird" for lack of a better word.

It's fine if you have a simple query like the above, however, when you start building more complex queries - this method tends to become hard to read and maintain.

Scopes allow you to build your own chainable Laravel methods. This way, you can group queries, perform other logic, and encapsulate your queries into a neat function.

This leads to more readable code throughout your application. For example:

```php
$posts = Post::where("status", "published")
   ->where("featured", 1)
   ->get()
```

You may display featured posts on several pages, therefore you'll need to keep repeating this same block over and over again.

With scopes, you can instead add a model method:
```php
class Post extends Model
{
    public function scopeFeaturedPosts($query)
    {
        return $query->where('status', 'published')
            ->where('featured', 1);
    }
}
```

Now in your code elsewhere:
```php
$posts = Post::featuredPosts()->get()
```

Great! Now we have a much cleaner model query and reusable code we can use throughout our application.

You will notice there is a convention being followed here:

1. Scopes must start with the prefix "scope".
2. When calling a scope, lowercase the first letter e.g. "FeaturedPosts" become "[f]eaturedPosts"

> ℹ️ Scopes also support parameters. Simply add n number of parameters after $query e.g. ```scopeFeaturedPosts($query, $category, $tag) ``` and pass them in like so: ```Post::featuredPosts($category, $tag)->get() ```

## Scopes are not just for clean code!

While you will usually use scopes to make your codebase a lot cleaner, this is not the only benefit of using scopes.

Another way to use scopes effectively is to use global scopes. Global scopes allow you to run a scope on every model query.

The best use case for this is multi-tenancy, if you are building a SaaS app, you probably would stamp each row in your tables with a "tenant_id".

Adding a "tenant_id" manually to every query can become cumbersome and tedious, not to mention error-prone if you forget to add the clause.

To add a global scope, simply add a similar method to your model class:
```php
protected static function booted()
{
    static::addGlobalScope('tenant_filter', function (Builder $builder) {
        $builder->where('tenant_id', auth()->user()->tenant_id);
    });
}

```

## Conclusion

Scopes are a nifty tool in your Laravel toolbox to make your code more readable and portable. Furthermore, scopes also provide more advanced functionality such as binding to model events like "deleting".

I glossed over a lot of concepts here on scopes, purely because this article is not a deep dive and intended to just get you started with scopes.

If you are interested in learning more about the advanced use cases, please comment down below and I will do a deep dive in a future article.