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
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
12cb726
Fix error when the scope a filter
aerni Apr 27, 2022
b42cfe9
Automatically apply the scopes to the builders
aerni Apr 27, 2022
396112b
Apply same magic to the tag scopes
aerni Apr 27, 2022
46ca391
Add method to manually register a scope
aerni Apr 27, 2022
73b257e
Move methods to the base builder
aerni Apr 27, 2022
c4696ca
Move logic to a trait
aerni Apr 27, 2022
9ff2ba9
Also apply the scopes when using a DB
aerni Apr 27, 2022
4e2f6f0
Use `get_class` instead of `::class`
aerni Apr 27, 2022
57aab9b
Also use `get_class` here
aerni Apr 27, 2022
5f0256e
Revert "Add method to manually register a scope"
aerni Apr 27, 2022
e9be34c
Fix typo
aerni Apr 27, 2022
f144d4d
Fix tests by filtering empty values
aerni Apr 27, 2022
f35527e
Add tests
aerni Apr 28, 2022
ae2167a
No need to set to an empty array
aerni Apr 28, 2022
b2d9387
Add tests
aerni Apr 28, 2022
014d06e
Fix styling
aerni May 13, 2022
d5b5570
Merge branch '3.3' of https:/aerni/cms into feature/query…
aerni Jun 15, 2022
ce7bc84
Merge branch '4.x' into feature/query-scopes
jasonvarga Jul 6, 2023
002e765
Merge branch '4.x' into pr/5927
duncanmcclean Dec 5, 2023
c0a7ddf
Eloquent queries: check if scope can be applied
duncanmcclean Dec 5, 2023
04e330b
Merge branch '4.x' into pr/5927
duncanmcclean Dec 5, 2023
ae6a277
Eloquent: move response to under scope check
duncanmcclean Dec 5, 2023
a380ed9
Pass arguments to query scope
duncanmcclean Dec 5, 2023
06b94c6
add array typehint
duncanmcclean Dec 5, 2023
718271f
Add Parameters to applyScope typehint
duncanmcclean Dec 5, 2023
d2c1c17
update tests to ensure arguments are passed along
duncanmcclean Dec 5, 2023
5d3a1ef
Merge branch '4.x' into pr/5927
duncanmcclean Feb 13, 2024
5fba217
Pint
duncanmcclean Feb 13, 2024
9852d59
Merge branch '5.x' into pr/5927
duncanmcclean May 13, 2024
5bb88e5
🍺
duncanmcclean May 13, 2024
92baa6e
Replace `snake_case` helper
duncanmcclean May 14, 2024
3142d95
Replace another usage of `snake_case`
duncanmcclean May 14, 2024
4e35c27
Merge branch '5.x' into feature/query-scopes
jasonvarga Sep 30, 2024
9cefa55
rework things ...
jasonvarga Oct 2, 2024
8df7a29
revert unnecessary changes
jasonvarga Oct 2, 2024
c86430b
handled in other tests
jasonvarga Oct 2, 2024
c53e9f9
prevent scope being applied via statamic and also on underlying eloqu…
jasonvarga Oct 2, 2024
33748ab
statamic scopes require arrays as context
jasonvarga Oct 3, 2024
762a1ab
unused
jasonvarga Oct 3, 2024
6cc4c56
revert. this was fixed on main branch already.
jasonvarga Oct 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@
use Statamic\Contracts\Query\Builder as Contract;
use Statamic\Extensions\Pagination\LengthAwarePaginator;
use Statamic\Facades\Pattern;
use Statamic\Query\Scopes\AppliesScopes;

abstract class Builder implements Contract
{
use AppliesScopes;

protected $columns;
protected $limit;
protected $offset = 0;
Expand All @@ -34,6 +37,13 @@ abstract class Builder implements Contract
'<=' => 'LessThanOrEqualTo',
];

public function __call($method, $args)
{
$this->applyScope($method);

return $this;
}

public function select($columns = ['*'])
{
$this->columns = $columns;
Expand Down
7 changes: 7 additions & 0 deletions src/Query/EloquentQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@
use Statamic\Contracts\Query\Builder;
use Statamic\Extensions\Pagination\LengthAwarePaginator;
use Statamic\Facades\Blink;
use Statamic\Query\Scopes\AppliesScopes;
use Statamic\Support\Arr;

abstract class EloquentQueryBuilder implements Builder
{
use AppliesScopes;

protected $builder;
protected $columns;

Expand All @@ -40,6 +43,10 @@ public function __call($method, $args)
{
$response = $this->builder->$method(...$args);

if ($this->canApplyScope($method)) {
$this->applyScope($method);
}

return $response instanceof EloquentBuilder ? $this : $response;
}

Expand Down
49 changes: 49 additions & 0 deletions src/Query/Scopes/AppliesScopes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace Statamic\Query\Scopes;

use Statamic\Facades\Scope;

trait AppliesScopes
{
public function applyScope($method, $context = [])
{
// Throw an exception if the scope doesn't exist.
if (! $scope = Scope::find(snake_case($method))) {
throw new \Exception("The [$method] scope does not exist.");
}

// Apply the scope to all builders if none were defined.
if ($scope->builders()->isEmpty()) {
return $scope->apply($this, $context);
}

// Only apply the scope to the defined builders.
if ($scope->builders()->contains(get_class($this))) {
return $scope->apply($this, $context);
}

// Throw an exception if a user is trying to access a scope that is not supported by this builder.
throw new \Exception('The ['.get_class($this)."] query builder does not support the [$method] scope.");
}

public function canApplyScope($method): bool
{
// If the scope doesn't exist, return false.
if (! $scope = Scope::find(snake_case($method))) {
return false;
}

// If no builders are defined, return true.
if ($scope->builders()->isEmpty()) {
return true;
}

// If builders are defined and this builder is one of them, return true.
if ($scope->builders()->contains(get_class($this))) {
return true;
}

return false;
}
}
14 changes: 14 additions & 0 deletions src/Query/Scopes/Scope.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

use Statamic\Extend\HasHandle;
use Statamic\Extend\RegistersItself;
use Statamic\Statamic;

abstract class Scope
{
use HasHandle, RegistersItself;

protected static $binding = 'scopes';
protected static $builders;

/**
* Apply the scope to a given query builder.
Expand All @@ -19,4 +21,16 @@ abstract class Scope
* @return void
*/
abstract public function apply($query, $values);

/**
* Return the query builders that this scope supports.
*
* @return \Illuminate\Support\Collection
*/
public static function builders()
{
return collect(static::$builders)->map(function ($builder) {
return get_class(Statamic::query($builder));
});
}
}
14 changes: 12 additions & 2 deletions src/Query/Scopes/ScopeRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,18 @@ public function all()

public function find($key, $context = [])
{
if ($scope = app('statamic.scopes')->get($key)) {
return app($scope)?->context($context);
if ($class = app('statamic.scopes')->get($key)) {
$scope = app($class);

if (! $scope) {
return null;
}

if ($scope instanceof Filter) {
$scope->context($context);
}

return $scope;
}
}

Expand Down
14 changes: 4 additions & 10 deletions src/Tags/Concerns/QueriesScopes.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,15 @@ trait QueriesScopes
{
public function queryScopes($query)
{
$this->parseQueryScopes()
->map(function ($handle) {
return app('statamic.scopes')->get($handle);
})
->filter()
->each(function ($class) use ($query) {
$scope = app($class);
$scope->apply($query, $this->params);
});
$this->parseQueryScopes()->each(function ($handle) use ($query) {
$query->applyScope($handle, $this->params);
});
}

protected function parseQueryScopes()
{
$scopes = Arr::getFirst($this->params, ['query_scope', 'filter']);

return collect(explode('|', $scopes ?? ''));
return collect(explode('|', $scopes))->filter();
}
}
19 changes: 19 additions & 0 deletions tests/Data/Assets/AssetQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Statamic\Facades\Asset;
use Statamic\Facades\AssetContainer;
use Statamic\Facades\Blueprint;
use Statamic\Query\Scopes\Scope;
use Tests\PreventSavingStacheItemsToDisk;
use Tests\TestCase;

Expand All @@ -28,6 +29,8 @@ public function setUp(): void
Storage::disk('test')->put('e.jpg', '');
Storage::disk('test')->put('f.jpg', '');
$this->container = tap(AssetContainer::make('test')->disk('test'))->save();

app('statamic.scopes')[CustomScope::handle()] = CustomScope::class;
}

/** @test */
Expand Down Expand Up @@ -524,6 +527,14 @@ public function it_can_get_assets_using_tap()
$this->assertEquals(['a'], $assets->map->filename()->all());
}

/** @test **/
public function assets_are_found_using_scopes()
{
$assets = $this->container->queryAssets()->customScope()->get();

$this->assertCount(1, $assets);
}

/** @test */
public function assets_are_found_using_offset()
{
Expand Down Expand Up @@ -601,3 +612,11 @@ public function querying_a_data_field_will_generate_meta_files()
], collect(Storage::disk('test')->allFiles())->sort()->values()->all());
}
}

class CustomScope extends Scope
{
public function apply($query, $params)
{
$query->where('filename', 'a');
}
}
27 changes: 27 additions & 0 deletions tests/Data/Entries/EntryQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,21 @@
use Statamic\Facades\Collection;
use Statamic\Facades\Entry;
use Statamic\Facades\Site;
use Statamic\Query\Scopes\Scope;
use Tests\PreventSavingStacheItemsToDisk;
use Tests\TestCase;

class EntryQueryBuilderTest extends TestCase
{
use PreventSavingStacheItemsToDisk;

public function setUp(): void
{
parent::setUp();

app('statamic.scopes')[CustomScope::handle()] = CustomScope::class;
}

private function createDummyCollectionAndEntries()
{
Collection::make('posts')->save();
Expand Down Expand Up @@ -655,6 +663,17 @@ public function it_substitutes_entries_by_uri_and_site()
$this->assertSame($found, $substituteFr);
}

/** @test **/
public function entries_are_found_using_scopes()
{
EntryFactory::id('1')->slug('post-1')->collection('posts')->data(['title' => 'Post 1'])->create();
EntryFactory::id('2')->slug('post-2')->collection('posts')->data(['title' => 'Post 2'])->create();

$entries = Entry::query()->customScope()->get();

$this->assertCount(1, $entries);
}

/** @test */
public function entries_are_found_using_offset()
{
Expand Down Expand Up @@ -738,3 +757,11 @@ public function likeProvider()
});
}
}

class CustomScope extends Scope
{
public function apply($query, $params)
{
$query->where('title', 'Post 1');
}
}
46 changes: 40 additions & 6 deletions tests/Data/Taxonomies/TermQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Statamic\Facades\Site;
use Statamic\Facades\Taxonomy;
use Statamic\Facades\Term;
use Statamic\Query\Scopes\Scope;
use Statamic\Taxonomies\LocalizedTerm;
use Statamic\Taxonomies\TermCollection;
use Tests\PreventSavingStacheItemsToDisk;
Expand All @@ -17,6 +18,13 @@ class TermQueryBuilderTest extends TestCase
{
use PreventSavingStacheItemsToDisk;

public function setUp(): void
{
parent::setUp();

app('statamic.scopes')[CustomScope::handle()] = CustomScope::class;
}

/** @test */
public function it_gets_terms()
{
Expand Down Expand Up @@ -253,39 +261,45 @@ public function it_filters_usage_in_collections()
Term::make('g')->taxonomy('cats')->data([])->save();
Term::make('h')->taxonomy('cats')->data([])->save();

$this->assertEquals(['cats::f', 'cats::g', 'tags::a', 'tags::c'],
$this->assertEquals(
['cats::f', 'cats::g', 'tags::a', 'tags::c'],
Term::query()
->where('collection', 'blog')
->get()->map->id()->sort()->values()->all()
);

$this->assertEquals(['tags::a', 'tags::c'],
$this->assertEquals(
['tags::a', 'tags::c'],
Term::query()
->where('collection', 'blog')
->where('taxonomy', 'tags')
->get()->map->id()->sort()->values()->all()
);

$this->assertEquals(['cats::f', 'cats::h', 'tags::a', 'tags::b'],
$this->assertEquals(
['cats::f', 'cats::h', 'tags::a', 'tags::b'],
Term::query()
->where('collection', 'news')
->get()->map->id()->sort()->values()->all()
);

$this->assertEquals(['tags::a', 'tags::b'],
$this->assertEquals(
['tags::a', 'tags::b'],
Term::query()
->where('collection', 'news')
->where('taxonomy', 'tags')
->get()->map->id()->sort()->values()->all()
);

$this->assertEquals(['cats::f', 'cats::g', 'cats::h', 'tags::a', 'tags::b', 'tags::c'],
$this->assertEquals(
['cats::f', 'cats::g', 'cats::h', 'tags::a', 'tags::b', 'tags::c'],
Term::query()
->whereIn('collection', ['blog', 'news'])
->get()->map->id()->sort()->values()->all()
);

$this->assertEquals(['tags::a', 'tags::b', 'tags::c'],
$this->assertEquals(
['tags::a', 'tags::b', 'tags::c'],
Term::query()
->whereIn('collection', ['blog', 'news'])
->where('taxonomy', 'tags')
Expand Down Expand Up @@ -587,6 +601,18 @@ public function terms_are_found_using_where_json_length()
$this->assertEquals(['2', '5', '4'], $entries->map->slug()->all());
}

/** @test **/
public function terms_are_found_using_scopes()
{
Taxonomy::make('tags')->save();
Term::make('a')->taxonomy('tags')->data(['title' => 'Post 1'])->save();
Term::make('b')->taxonomy('tags')->data(['title' => 'Post 2'])->save();

$entries = Term::query()->customScope()->get();

$this->assertCount(1, $entries);
}

/** @test */
public function terms_are_found_using_offset()
{
Expand All @@ -602,3 +628,11 @@ public function terms_are_found_using_offset()
$this->assertEquals(['b', 'c'], $terms->map->slug()->all());
}
}

class CustomScope extends Scope
{
public function apply($query, $params)
{
$query->where('title', 'Post 1');
}
}
Loading
Loading