Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5.x] Support scopes as query methods #5927

Merged
merged 40 commits into from
Oct 3, 2024

Conversation

aerni
Copy link
Contributor

@aerni aerni commented Apr 27, 2022

This PR implements the new query scopes approach as discussed in PR #5869.

Each scope in the App\Scopes namespace is now also available to the query builders.

Example

Let's assume we've got a App\Scopes\PaymentSuccessful scope class. This scope can now be used in Antlers and in PHP.

// Antlers
{{ collection:blog filter="payment_successful" }}

// PHP
Entry::query()->paymentSuccessful()

The query method corresponds to the scope's handle in camel case.

// The handle in App\Scopes\PaymentSuccessful
protected static $handle = 'where_something';

Entry::query()->whereSomething()

By default, each scope is available to all query builders. You may want to restrict the scope to be available on certain query builders only. You can do that by adding the alias of the query builder to the $builders property.

// The scope is only available to the EntryQueryBuilder
protected static $builders = ['entries'];

I also added some nice exceptions. But you might decide to remove them for a more graceful handling if the scope doesn't exist.

This should fix the error `Cannot use ::class with dynamic class name`
This static property is collected in the `builders` method, which will always return a collection.
@aerni
Copy link
Contributor Author

aerni commented Apr 28, 2022

I added a bunch of tests. The only thing I wasn't sure about was how to test the eloquent builder for entries and users?

aerni and others added 3 commits May 13, 2022 15:40
…-scopes

# Conflicts:
#	src/Tags/Concerns/QueriesScopes.php
#	tests/Data/Assets/AssetQueryBuilderTest.php
#	tests/Data/Entries/EntryQueryBuilderTest.php
#	tests/Data/Taxonomies/TermQueryBuilderTest.php
# Conflicts:
#	src/Query/EloquentQueryBuilder.php
@jasonvarga jasonvarga changed the base branch from 3.3 to 4.x July 6, 2023 20:56
@jasonvarga jasonvarga changed the title Apply scopes to query builders [4.x] Apply scopes to query builders Jul 6, 2023
Copy link
Member

@duncanmcclean duncanmcclean left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this - it looks good! I've noticed one small thing though... 🤔

src/Query/EloquentQueryBuilder.php Outdated Show resolved Hide resolved
This allows the query builder to fallback to scopes on Eloquent models.
@duncanmcclean duncanmcclean changed the title [4.x] Apply scopes to query builders [5.x] Apply scopes to query builders May 13, 2024
@duncanmcclean duncanmcclean changed the base branch from 4.x to 5.x May 13, 2024 12:15
duncanmcclean and others added 8 commits May 13, 2024 13:19
# Conflicts:
#	tests/Data/Assets/AssetQueryBuilderTest.php
#	tests/Data/Entries/EntryQueryBuilderTest.php
#	tests/Data/Taxonomies/TermQueryBuilderTest.php
#	tests/Data/Users/UserQueryBuilderTest.php
- Scopes are not automatically available on query builder. You register them through facade. eg Entry::allowQueryScope(ScopeName::class);
- If calling a method that doesnt correspond to a scope, throw an "undefined method" error like you'd get before
- Reverts changes to tag query scoping
@jasonvarga jasonvarga changed the title [5.x] Apply scopes to query builders [5.x] Support scopes as query methods Oct 2, 2024
@jasonvarga
Copy link
Member

Ahem, sorry for the delay. 🫠

We talked through how we thought this should work and have made some changes.

Scopes aren't automatically applied to all builders. You have to opt into them. You can do that in a provider:

Entry::allowQueryScope(MichaelsScope::class); // handle of michaels_scope
// Allows $query->michaelsScope()

You can set the method in the second argument:

Entry::allowQueryScope(MyScope::class, 'whereMichael');
// Allows $query->whereMichael()

This made it feel more close to how it works in Laravel. In Laravel you would allow these "local scopes" by adding methods to the Model. Since you aren't touching the models in Statamic, opting in like this was an alternative.

@aerni
Copy link
Contributor Author

aerni commented Oct 2, 2024

I like it. Would it make sense to also scope the scope (no pun intended) to a collection? A PaymentSuccessful scope probably doesn't make sense for all collections.

@jasonvarga
Copy link
Member

I get the request, but not really. The entry query builder can query across collections.
In your case your PaymentSuccessful scope could add $query->where('collection', 'orders').

Also, I've adjusted the PR so that context needs to be passed into the method as an array. This is different to Laravel, but Statamic scopes are different. We may be able to change it for v6.

i.e. Instead of ->myScope($arg) you'd need to do ->myScope(['foo' => $foo]) since our scopes' apply method expects an array.

function apply($query, $values) {
  $foo = $values['foo'];
  // ...
}

@jasonvarga jasonvarga merged commit 7de9f95 into statamic:5.x Oct 3, 2024
16 checks passed
@ryanmitchell
Copy link
Contributor

I have been waiting so long for this. Thank you!!

@jasonvarga
Copy link
Member

Pfft relax it was only opened in April 2022.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants