How to use a scope with a query builder in Statamic

Published December 5th, 2023

Scopes in Statamic are a great way to encapsulate query logic in a single, central and re-usable place. Query logic can get complex, and keeping that in one place can help ensure your queries return the expected results wherever they’re called from.

When using the collection tag, you can provide a query_scope parameter, and the name of your scope, and have it applied to the inner workings of the query driving the collection tag.

And it is also possible to use your query scope in your own manually-coded Query Builder instances too.

To get started, you’ll need your scope. Because Statamic is so polite, ask nicely, and it can make the stub for you:

1php please make:scope FeaturedTagScope
Copied!

This will live in your app/Scopes folder, and be discovered by Statamic automagically when applied.

Your scope will have an apply method that receives a Query Builder instance ($query), plus an array of contextual $values that you can use within your logic: if you’re using the collection tag, this is the parameters. But if you’re calling it for your own use case, you can pass whatever values your app needs. Let me explain that…

A feature request came through for Feedamic (my awesome RSS feed generator for Statamic) that was asking to support query scopes for specific feeds so that feeds can more easily include/exclude Entries based on your app’s behaviour. Great idea, right?

In this instance, for the $values, the configuration of the feed being generated is passed – so this way the developer writing the scope knows what the feed is, and the config of the feed, so they can write their own logic.

Make sense?

The docs have a nice and simple scope that simply looks for the featured parameter to be true – but you could apply whatever logic you want – even by querying your taxonomy terms if that works for you:

1<?php
2 
3namespace App\Scopes;
4 
5use Statamic\Query\Scopes\Scope;
6 
7class FeaturedTagScope extends Scope
8{
9 /**
10 * Apply the scope.
11 *
12 * @param \Statamic\Query\Builder $query
13 * @param array $values
14 * @return void
15 */
16 public function apply($query, $values)
17 {
18 $query->whereTaxonomy('tags::featured');
19 }
20}
Copied!

So for the collection tag, you’ll get the parameter values, and within Feedamic you’ll get the feed config, so those $values are really up to you as the initiator of the scope to determine what gets passed – and that will change based on your use case: it’s your app, your logic, so pass what is contextually important to help your scope function correctly.

Righto, so that’s all about the scope – which is all well and good (and even documented if you want a read) – but how do we actually call that from a different query builder instance. 

So Feedamic gets a list of entries that should be in the feed using Statamic’s Entry Query Builder. Feedamic does what it needs to do – makes sure it is the right site, the right collection, meets the feeds configuration, is published – all that fun stuff. But then it also needs to use the scope. 

This is the simple part – so obviously simple you might (like me) over look it to start too.

When you created your scope, you filled out your apply method – and this is a public function, that accepts a Query Builder.

Hang on a sec – we’re working with a Query Builder instance, and have a scope file – so… let’s simply instantiate our scope from Laravel’s container, and pass it the Query Builder we’ve already been creating:

1use App\Scopes\FeaturedTagScope;
2use Statamic\Facades\Entry;
3 
4// start your query builder, with whatever logic you need
5$queryBuilder = Entry::query()
6 ->where('collection', 'case_studies')
7 // and whatever other logic you need
8;
9 
10// instantiate your scope, and call its apply method
11// your $params can be whatever contextual params your
12// scope needs to do its thang
13app(FeaturedTagScope::class)->apply($queryBuilder, [
14 'contextual' => 'params',
15 'can' => 'go here'
16]);
17 
18// get your results
19$results = $queryBuilder->get();
Copied!

The logic within the scope will be applied to our Query Builder instance: and that’s it. It really is that simple.

All you need to do is:

  1. create your scope (and write its awesome logic)

  2. create your own Query Builder where you need it

  3. instantiate your scope and call its apply method

Don’t forget too: you can also extend your scope to be a filter for use within the UI – check out the docs for that too.

You may be interested in...