DEV Community

loading...

The magic of query scopes in Laravel

bertheyman profile image Bert Heyman Updated on ・2 min read

Let's say you're building a site where people can enter book suggestions for others. As a teaser, the homepage will contain 5 book suggestions. The query could be as simple as:

// A simple select
Book::limit(5)->get();
Enter fullscreen mode Exit fullscreen mode

Now, you might have selected some low quality book suggestions, or the horcrux book with knowledge that better stays hidden forever. You decide that book suggestions, in order to be published on the homepage, should

  • be set to visible
  • created during the last month
  • have at least 10 upvotes
// Some quality control
Book::where('visible', 1)
    ->where('created_at', '>', Carbon::now()->subMonth())
    ->whereHas('votes', '>=', 10)
    ->limit(5)
    ->get();
Enter fullscreen mode Exit fullscreen mode

This gives you some perfect book suggestions, so you're satisfied and set aside some time to to read some of them. But after a while, you end up needing a lot more checks to improve the quality of the suggestions. This might make the query a bit messy or harder to read.

At this point, you might consider spitting your query using query scopes for. By breaking your query into building blocks, it becomes easier to read and reuse.

A refactor with query scopes

Have a look at the following refactor with query scopes from Laravel:

// App\Http\Controllers\BookController.php

// This syntax is very readable and easy to reuse
// Every building block is defined on your model
Book::popular()
    ->createdAfter(Carbon::now()->subMonth())
    ->limit(5)
    ->get();

// App\Book.php (model)

protected static function boot()
{
    parent::boot();

    // A global scope is applied to all queries on this model
    // -> No need to specify visibility restraints on every query
    static::addGlobalScope('visible', function (Builder $builder) {
        $builder->where('visible', 1);
    });
    // Bonus: if multiple models are hideable, this behaviour might
    // belong in a specific scope for easy reuse
}

// These are local scopes: ->popular() is added to the query to apply this where statement
public function scopePopular($query)
{
    // By defining the conditions to be a popular book,
    // it's easy to change them later on for all queries at once
    return $query->whereHas('votes', '>=', 10);
}

public function scopeCreatedAfter($query, $date)
{
    // A scope can be dynamic and accept parameters
    return $query->where('created_at', '>', $date)
}

Enter fullscreen mode Exit fullscreen mode

It's not always useful to use scopes, but I've found them particulary useful when feeling the need for smaller, reusable blocks in my queries or when struggling with readability.

I hope you've learned something if you didn't use query scopes before.

How have you wrestled with (more complex) queries?
Feel free to leave a comment to tell about:

  • Any useful tips for beginners missing in this article?
  • What you'd do differently in the query from the example?
  • Patterns you use for organising queries in larger projects. Repositories, query scopes, a mix or something completely different?

Further reading: Laravel docs on query scopes (check if that Laravel version is still the most recent one)
Follow up article: Breaking model functionality down in reusable components

Discussion (4)

pic
Editor guide
Collapse
mohammadfouladgar profile image
Fouladgar.dev

Thanks for the nice post.

We can also use the Scopes in custom Filters.
check out this article, please:

dev.to/mohammadfouladgar/making-th...

Collapse
bertheyman profile image
Bert Heyman Author

Glad you liked the article.
Thanks for sharing this, Fouladgar!

Collapse
nate6762x profile image
Nate Granger

"Any useful tips for beginners missing in this article?" Really?
I'm one of your “beginners,” a laravel noob (3 months full time), 2 years studying webdev & OOP on my own. This article is exactly the kind of ubiquitous “training” that frustrates me. From what I can tell, this column is only for experienced programmers, and they probably don't need it anyway. Maybe at best it would be something confirming for them to read.
Why, you may ask? Nowhere in this article do you define “query scope.” You don’t say what it is; you don’t point to it; you suggest how it helps you but are not specific how that happens. You present 12 lines of code, and say “look at” it. It’s like teaching landscape painting by taking the student outside, gesturing at the view, and saying “See?” No, I don’t see. You don’t explain what was done or why and how it was an improvement. So you’re right, it truly is magic; I see an outcome and have no idea what trick you used to get there. Some of these things I might guess at, but was that your intention? If it’s for beginners, wouldn’t you expect it to be explicit? (eg “They removed X from the query and put it in its own separate function, … and that is why it’s known as query scope”.) After dissecting this article (for the purpose of this comment), I haven’t the slightest idea of what query scope is, when, where, why or how to use it. I know what a scope is, and what a query is, but taken together they are apparently a term of art whose meaning cannot be deduced from its parts.
I am pushing 70, have decades of work experience, much of it in training. I’d love to have your knowledge to be able to impart it to others, but I could certainly help you do it if that’s your actual intention. Also, be aware it’s not just you. Almost EVERY such “explanation” out there has the same flaw, particularly the Laravel docs.
Please take this as constructive (albeit harsh) criticism, meant to improve your effort to share your gift. If you would like to correspond further, I would be happy to engage.

Collapse
bertheyman profile image
Bert Heyman Author • Edited

Hi, thanks for sharing your feedback! I'll have a look where some more context might be useful.

Actually, query scopes might not always be the right choice. They're merely a syntax that allows you to split queries into small, clean and reusable blocks. This might improve the readability and allow for easier tweaks at a later time.

If you are interested in learning the "why" and not just the "what" and miss this in the docs, Laracasts might be a great resource on your journey of learning Laravel. Good luck!