From 7068857bfb0fc73daff25bb4ef4b0fa7b5c93a59 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Thu, 20 Jul 2023 21:43:49 +0100 Subject: [PATCH 01/15] Support whereHas / whereRelation in Entry and User query builders --- src/Fields/Fieldtype.php | 5 + src/Fieldtypes/Entries.php | 8 + src/Fieldtypes/Users.php | 5 + src/Query/Builder.php | 2 + src/Query/Traits/QueriesRelationships.php | 230 ++++++++++++++++++++++ src/Stache/Query/EntryQueryBuilder.php | 23 +++ src/Stache/Query/UserQueryBuilder.php | 6 + 7 files changed, 279 insertions(+) create mode 100644 src/Query/Traits/QueriesRelationships.php diff --git a/src/Fields/Fieldtype.php b/src/Fields/Fieldtype.php index e91e4d29e5..e7992d487c 100644 --- a/src/Fields/Fieldtype.php +++ b/src/Fields/Fieldtype.php @@ -350,6 +350,11 @@ public function isRelationship(): bool return $this->relationship; } + public function relationshipQueryBuilder() + { + return false; + } + public function toQueryableValue($value) { return $value; diff --git a/src/Fieldtypes/Entries.php b/src/Fieldtypes/Entries.php index bc7451f3d7..6b2eb98730 100644 --- a/src/Fieldtypes/Entries.php +++ b/src/Fieldtypes/Entries.php @@ -352,4 +352,12 @@ protected function getItemsForPreProcessIndex($values): SupportCollection ? collect([$augmented]) : $augmented->whereAnyStatus()->get(); } + + public function relationshipQueryBuilder() + { + $collections = $this->config('collections'); + + return Entry::query() + ->when($collections, fn ($query) => $query->whereIn('collection', $collections)); + } } diff --git a/src/Fieldtypes/Users.php b/src/Fieldtypes/Users.php index 4bec003c50..9cb6ce6a89 100644 --- a/src/Fieldtypes/Users.php +++ b/src/Fieldtypes/Users.php @@ -197,4 +197,9 @@ public function filter() { return new UserFilter($this); } + + public function relationshipQueryBuilder() + { + return User::query(); + } } diff --git a/src/Query/Builder.php b/src/Query/Builder.php index 39014b0b21..fb3f886663 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -13,6 +13,8 @@ abstract class Builder implements Contract { + use Traits\QueriesRelationships; + protected $columns; protected $limit; protected $offset = 0; diff --git a/src/Query/Traits/QueriesRelationships.php b/src/Query/Traits/QueriesRelationships.php new file mode 100644 index 0000000000..1846c974c2 --- /dev/null +++ b/src/Query/Traits/QueriesRelationships.php @@ -0,0 +1,230 @@ +=', $count = 1, $boolean = 'and', Closure $callback = null) + { + [$relationQueryBuilder, $relationField] = $this->getRelationQueryBuilderAndField($relation); + + if (! $callback) { + return $this->whereJsonLength($relation, $operator, $count, $boolean); + } + + $ids = $relationQueryBuilder + ->where($callback) + ->get(['id']) + ->map(fn ($item) => $item->id()) + ->all(); + + $maxItems = $relationField->config()['max_items'] ?? 0; + + if ($maxItems == 1) { + return $this->whereIn($relation, $ids); + } + + if (empty($ids)) { + return $this->whereJsonContains($relation, ['']); + } + + return $this->where(function ($subquery) use ($relation, $ids) { + foreach ($ids as $count => $id) { + $subquery->{$count == 0 ? 'whereJsonContains' : 'orWhereJsonContains'}($relation, [$id]); + } + }); + } + + /** + * Add a relationship count / exists condition to the query with an "or". + * + * @param string $relation + * @param string $operator + * @param int $count + * @return \Statamic\Query\Builder|static + */ + public function orHas($relation, $operator = '>=', $count = 1) + { + return $this->has($relation, $operator, $count, 'or'); + } + + /** + * Add a relationship count / exists condition to the query. + * + * @param string $relation + * @param string $boolean + * @param \Closure|null $callback + * @return \Statamic\Query\Builder|static + */ + public function doesntHave($relation, $boolean = 'and', Closure $callback = null) + { + return $this->{$boolean == 'and' ? 'where' : 'orwhere'}(function ($subquery) use ($relation, $callback) { + return $subquery->whereNull($relation) + ->orHas($relation, '<', 1, 'and', $callback); + }); + } + + /** + * Add a relationship count / exists condition to the query with an "or". + * + * @param string $relation + * @return \Statamic\Query\Builder|static + */ + public function orDoesntHave($relation) + { + return $this->doesntHave($relation, 'or'); + } + + /** + * Add a relationship count / exists condition to the query with where clauses. + * + * @param string $relation + * @param \Closure|null $callback + * @param string $operator + * @param int $count + * @return \Statamic\Query\Builder|static + */ + public function whereHas($relation, Closure $callback = null, $operator = '>=', $count = 1) + { + return $this->has($relation, $operator, $count, 'and', $callback); + } + + /** + * Add a relationship count / exists condition to the query with where clauses and an "or". + * + * @param string $relation + * @param \Closure|null $callback + * @param string $operator + * @param int $count + * @return \Statamic\Query\Builder|static + */ + public function orWhereHas($relation, Closure $callback = null, $operator = '>=', $count = 1) + { + return $this->has($relation, $operator, $count, 'or', $callback); + } + + /** + * Add a relationship count / exists condition to the query with where clauses. + * + * @param string $relation + * @param \Closure|null $callback + * @return \Statamic\Query\Builder|static + */ + public function whereDoesntHave($relation, Closure $callback = null) + { + return $this->doesntHave($relation, 'and', $callback); + } + + /** + * Add a relationship count / exists condition to the query with where clauses and an "or". + * + * @param string $relation + * @param \Closure|null $callback + * @return \Statamic\Query\Builder|static + */ + public function orWhereDoesntHave($relation, Closure $callback = null) + { + return $this->doesntHave($relation, 'or', $callback); + } + + /** + * Add a basic where clause to a relationship query. + * + * @param string $relation + * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column + * @param mixed $operator + * @param mixed $value + * @return \Statamic\Query\Builder|static + */ + public function whereRelation($relation, $column, $operator = null, $value = null) + { + return $this->whereHas($relation, function ($query) use ($column, $operator, $value) { + if ($column instanceof Closure) { + $column($query); + } else { + $query->where($column, $operator, $value); + } + }); + } + + /** + * Add an "or where" clause to a relationship query. + * + * @param string $relation + * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column + * @param mixed $operator + * @param mixed $value + * @return \Statamic\Query\Builder|static + */ + public function orWhereRelation($relation, $column, $operator = null, $value = null) + { + return $this->orWhereHas($relation, function ($query) use ($column, $operator, $value) { + if ($column instanceof Closure) { + $column($query); + } else { + $query->where($column, $operator, $value); + } + }); + } + + /** + * Get the blueprints avaialble to this query builder + * + * @return \Illuminate\Support\Collection + */ + protected function getBlueprintsForRelations() + { + return collect(); + } + + /** + * Get the "has relation" base query instance. + * + * @param string $relation + * @return \Statamic\Query\Builder + */ + protected function getRelationQueryBuilderAndField($relation) + { + $relationField = $this->getBlueprintsForRelations() + ->flatMap(function ($blueprint) use ($relation) { + return $blueprint->fields()->all()->map(function ($field) use ($relation) { + if ($field->handle() == $relation && $field->fieldtype()->isRelationship()) { + return $field; + } + }) + ->filter() + ->values(); + }) + ->filter() + ->first(); + + if (! $relationField) { + throw new InvalidArgumentException("Relation {$relation} does not exist"); + } + + $queryBuilder = $relationField->fieldtype()->relationshipQueryBuilder(); + + if (! $queryBuilder) { + throw new InvalidArgumentException("Relation {$relation} does not support subquerying"); + } + + return [$queryBuilder, $relationField]; + } +} diff --git a/src/Stache/Query/EntryQueryBuilder.php b/src/Stache/Query/EntryQueryBuilder.php index e43fe045b5..bce322a4f1 100644 --- a/src/Stache/Query/EntryQueryBuilder.php +++ b/src/Stache/Query/EntryQueryBuilder.php @@ -131,4 +131,27 @@ protected function getWhereColumnKeyValuesByIndex($column) return $this->getWhereColumnKeysFromStore($collection, ['column' => $column]); }); } + + protected function getBlueprintsForRelations() + { + $wheres = collect($this->wheres); + + $collections = $wheres->where('column', 'collection') + ->flatMap(function ($where) { + return $where['values'] ?? [$where['value']] ?? []; + }) + ->unique(); + + if (! $collections->count()) { + $collections = Facades\Collection::all(); + } + + return $collections->flatMap(function ($collectionHandle) { + if ($collection = Facades\Collection::find($collectionHandle)) { + return $collection->entryBlueprints(); + } + }) + ->filter() + ->unique(); + } } diff --git a/src/Stache/Query/UserQueryBuilder.php b/src/Stache/Query/UserQueryBuilder.php index 6f821daac2..3664723d82 100644 --- a/src/Stache/Query/UserQueryBuilder.php +++ b/src/Stache/Query/UserQueryBuilder.php @@ -3,6 +3,7 @@ namespace Statamic\Stache\Query; use Statamic\Auth\UserCollection; +use Statamic\Facades\User; class UserQueryBuilder extends Builder { @@ -50,4 +51,9 @@ protected function getOrderKeyValuesByIndex() return [$orderBy->sort => $items]; }); } + + protected function getBlueprintsForRelations() + { + return collect([User::make()->blueprint()]); + } } From aef4da1287d9f6ebeae35a3183dc5afdd021b787 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Thu, 20 Jul 2023 21:50:45 +0100 Subject: [PATCH 02/15] Better comments --- src/Query/Traits/QueriesRelationships.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Query/Traits/QueriesRelationships.php b/src/Query/Traits/QueriesRelationships.php index 1846c974c2..47fe58b7d2 100644 --- a/src/Query/Traits/QueriesRelationships.php +++ b/src/Query/Traits/QueriesRelationships.php @@ -185,7 +185,7 @@ public function orWhereRelation($relation, $column, $operator = null, $value = n } /** - * Get the blueprints avaialble to this query builder + * Get the blueprints available to this query builder * * @return \Illuminate\Support\Collection */ @@ -195,7 +195,7 @@ protected function getBlueprintsForRelations() } /** - * Get the "has relation" base query instance. + * Get the query builder and field for the relation we are querying (if they exist) * * @param string $relation * @return \Statamic\Query\Builder From eb3c22ff901d168e73d7ddf99cfa0ea6cee7ac96 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Thu, 20 Jul 2023 22:01:35 +0100 Subject: [PATCH 03/15] Fix return type --- src/Query/Traits/QueriesRelationships.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Query/Traits/QueriesRelationships.php b/src/Query/Traits/QueriesRelationships.php index 47fe58b7d2..be51b289a1 100644 --- a/src/Query/Traits/QueriesRelationships.php +++ b/src/Query/Traits/QueriesRelationships.php @@ -19,7 +19,7 @@ trait QueriesRelationships * @param \Closure|null $callback * @return \Statamic\Query\Builder|static * - * @throws \RuntimeException + * @throws \InvalidArgumentException */ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null) { From cf20adee658fa0cdcfcacb397729519124c3bd94 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Thu, 20 Jul 2023 22:02:39 +0100 Subject: [PATCH 04/15] Don't need these here anymore --- src/Query/Traits/QueriesRelationships.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Query/Traits/QueriesRelationships.php b/src/Query/Traits/QueriesRelationships.php index be51b289a1..afe2f2f9b5 100644 --- a/src/Query/Traits/QueriesRelationships.php +++ b/src/Query/Traits/QueriesRelationships.php @@ -4,8 +4,6 @@ use Closure; use InvalidArgumentException; -use Statamic\Facades; -use Statamic\Fieldtypes; trait QueriesRelationships { From 3ea9f368fbca37d2f24e88b7f9ae34d46c5e6814 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Fri, 21 Jul 2023 07:17:09 +0100 Subject: [PATCH 05/15] Add some test coverage --- src/Query/Traits/QueriesRelationships.php | 31 ++++-- src/Stache/Query/EntryQueryBuilder.php | 8 +- tests/Data/Entries/EntryQueryBuilderTest.php | 100 +++++++++++++++++++ tests/Data/Users/UserQueryBuilderTest.php | 91 +++++++++++++++++ 4 files changed, 218 insertions(+), 12 deletions(-) diff --git a/src/Query/Traits/QueriesRelationships.php b/src/Query/Traits/QueriesRelationships.php index afe2f2f9b5..5a674a2dfe 100644 --- a/src/Query/Traits/QueriesRelationships.php +++ b/src/Query/Traits/QueriesRelationships.php @@ -24,7 +24,7 @@ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', C [$relationQueryBuilder, $relationField] = $this->getRelationQueryBuilderAndField($relation); if (! $callback) { - return $this->whereJsonLength($relation, $operator, $count, $boolean); + return $this->{$boolean == 'and' ? 'whereJsonLength' : 'orWhereJsonLength'}($relation, $operator, $count, $boolean); } $ids = $relationQueryBuilder @@ -35,17 +35,33 @@ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', C $maxItems = $relationField->config()['max_items'] ?? 0; + $negate = in_array($operator, ['!=', '<']); + + if ($count != 1) { + throw new InvalidArgumentException("Counting with callbacks in has clauses is not supported"); + } + if ($maxItems == 1) { - return $this->whereIn($relation, $ids); + $method = $boolean == 'and' ? 'whereIn' : 'orWhereIn'; + if ($negate) { + $method = str_replace('here', 'hereNot', $method); + } + + return $this->$method($relation, $ids); } if (empty($ids)) { - return $this->whereJsonContains($relation, ['']); + return $this->{$boolean == 'and' ? 'whereJsonContains' : 'orWhereJsonContains'}($relation, ['']); } - return $this->where(function ($subquery) use ($relation, $ids) { + return $this->{$boolean == 'and' ? 'where' : 'orWhere'}(function ($subquery) use ($ids, $negate, $relation) { foreach ($ids as $count => $id) { - $subquery->{$count == 0 ? 'whereJsonContains' : 'orWhereJsonContains'}($relation, [$id]); + $method = $count == 0 ? 'whereJsonContains' : 'orWhereJsonContains'; + if ($negate) { + $method = str_replace('Contains', 'DoesntContain', $method); + } + + $subquery->$method($relation, [$id]); } }); } @@ -73,10 +89,7 @@ public function orHas($relation, $operator = '>=', $count = 1) */ public function doesntHave($relation, $boolean = 'and', Closure $callback = null) { - return $this->{$boolean == 'and' ? 'where' : 'orwhere'}(function ($subquery) use ($relation, $callback) { - return $subquery->whereNull($relation) - ->orHas($relation, '<', 1, 'and', $callback); - }); + return $this->has($relation, '<', 1, $boolean, $callback); } /** diff --git a/src/Stache/Query/EntryQueryBuilder.php b/src/Stache/Query/EntryQueryBuilder.php index bce322a4f1..13901b27c0 100644 --- a/src/Stache/Query/EntryQueryBuilder.php +++ b/src/Stache/Query/EntryQueryBuilder.php @@ -146,10 +146,12 @@ protected function getBlueprintsForRelations() $collections = Facades\Collection::all(); } - return $collections->flatMap(function ($collectionHandle) { - if ($collection = Facades\Collection::find($collectionHandle)) { - return $collection->entryBlueprints(); + return $collections->flatMap(function ($collection) { + if (is_string($collection)) { + $collection = Facades\Collection::find($collection); } + + return $collection ? $collection->entryBlueprints() : false; }) ->filter() ->unique(); diff --git a/tests/Data/Entries/EntryQueryBuilderTest.php b/tests/Data/Entries/EntryQueryBuilderTest.php index 3e4077338f..0a0dbe11e2 100644 --- a/tests/Data/Entries/EntryQueryBuilderTest.php +++ b/tests/Data/Entries/EntryQueryBuilderTest.php @@ -664,4 +664,104 @@ public function entries_are_found_using_offset() $this->assertCount(2, $entries); $this->assertEquals(['Post 2', 'Post 3'], $entries->map->title->all()); } + + /** @test **/ + public function entries_are_found_using_where_has_when_max_items_1() + { + $blueprint = Blueprint::makeFromFields(['entries_field' => ['type' => 'entries', 'max_items' => 1]]); + Blueprint::shouldReceive('in')->with('collections/posts')->andReturn(collect(['posts' => $blueprint])); + + $this->createDummyCollectionAndEntries(); + + Entry::find(1) + ->merge([ + 'entries_field' => 2 + ]) + ->save(); + + Entry::find(3) + ->merge([ + 'entries_field' => 1 + ]) + ->save(); + + $entries = Entry::query()->whereHas('entries_field', function ($subquery) { + $subquery->where('title', 'Post 2'); + }) + ->get(); + + $this->assertCount(1, $entries); + $this->assertEquals(['Post 1'], $entries->map->title->all()); + + $entries = Entry::query()->whereDoesntHave('entries_field', function ($subquery) { + $subquery->where('title', 'Post 2'); + }) + ->get(); + + $this->assertCount(2, $entries); + $this->assertEquals(['Post 2', 'Post 3'], $entries->map->title->all()); + } + + /** @test **/ + public function entries_are_found_using_where_has_when_max_items_not_1() + { + $blueprint = Blueprint::makeFromFields(['entries_field' => ['type' => 'entries']]); + Blueprint::shouldReceive('in')->with('collections/posts')->andReturn(collect(['posts' => $blueprint])); + + $this->createDummyCollectionAndEntries(); + + Entry::find(1) + ->merge([ + 'entries_field' => [2, 1] + ]) + ->save(); + + Entry::find(3) + ->merge([ + 'entries_field' => [1, 2] + ]) + ->save(); + + $entries = Entry::query()->whereHas('entries_field', function ($subquery) { + $subquery->where('title', 'Post 2'); + }) + ->get(); + + $this->assertCount(2, $entries); + $this->assertEquals(['Post 1', 'Post 3'], $entries->map->title->all()); + + $entries = Entry::query()->whereDoesntHave('entries_field', function ($subquery) { + $subquery->where('title', 'Post 2'); + }) + ->get(); + + $this->assertCount(1, $entries); + $this->assertEquals(['Post 2'], $entries->map->title->all()); + } + + /** @test **/ + public function entries_are_found_using_where_relation() + { + $blueprint = Blueprint::makeFromFields(['entries_field' => ['type' => 'entries']]); + Blueprint::shouldReceive('in')->with('collections/posts')->andReturn(collect(['posts' => $blueprint])); + + $this->createDummyCollectionAndEntries(); + + Entry::find(1) + ->merge([ + 'entries_field' => [2, 1] + ]) + ->save(); + + Entry::find(3) + ->merge([ + 'entries_field' => [1, 2] + ]) + ->save(); + + $entries = Entry::query()->whereRelation('entries_field', 'title', 'Post 2')->get(); + + $this->assertCount(2, $entries); + $this->assertEquals(['Post 1', 'Post 3'], $entries->map->title->all()); + } } diff --git a/tests/Data/Users/UserQueryBuilderTest.php b/tests/Data/Users/UserQueryBuilderTest.php index 6b3876074b..c76585694b 100644 --- a/tests/Data/Users/UserQueryBuilderTest.php +++ b/tests/Data/Users/UserQueryBuilderTest.php @@ -2,6 +2,10 @@ namespace Tests\Data\Users; +use Facades\Tests\Factories\EntryFactory; +use Statamic\Facades\Blueprint; +use Statamic\Facades\Collection; +use Statamic\Facades\Entry; use Statamic\Facades\User; use Tests\PreventSavingStacheItemsToDisk; use Tests\TestCase; @@ -208,4 +212,91 @@ public function users_are_found_using_tap() $this->assertCount(1, $users); $this->assertEquals(['Gandalf'], $users->map->name->all()); } + + /** @test **/ + public function users_are_found_using_where_has_when_max_items_1() + { + $this->createDummyCollectionAndEntries(); + + $blueprint = Blueprint::makeFromFields(['entries_field' => ['type' => 'entries', 'max_items' => 1]]); + Blueprint::shouldReceive('find')->with('user')->andReturn($blueprint); + + User::make()->email('gandalf@precious.com')->data(['name' => 'Gandalf', 'entries_field' => 2])->save(); + User::make()->email('smeagol@precious.com')->data(['name' => 'Smeagol'])->save(); + User::make()->email('frodo@precious.com')->data(['name' => 'Frodo', 'entries_field' => 1])->save(); + + $entries = User::query()->whereHas('entries_field', function ($subquery) { + $subquery->where('title', 'Post 2'); + }) + ->get(); + + $this->assertCount(1, $entries); + $this->assertEquals(['Gandalf'], $entries->map->name->all()); + + $entries = User::query()->whereDoesntHave('entries_field', function ($subquery) { + $subquery->where('title', 'Post 2'); + }) + ->get(); + + $this->assertCount(2, $entries); + $this->assertEquals(['Smeagol', 'Frodo'], $entries->map->name->all()); + } + + /** @test **/ + public function users_are_found_using_where_has_when_max_items_not_1() + { + $this->createDummyCollectionAndEntries(); + + $blueprint = Blueprint::makeFromFields(['entries_field' => ['type' => 'entries']]); + Blueprint::shouldReceive('find')->with('user')->andReturn($blueprint); + + User::make()->email('gandalf@precious.com')->data(['name' => 'Gandalf', 'entries_field' => [2, 1]])->save(); + User::make()->email('smeagol@precious.com')->data(['name' => 'Smeagol'])->save(); + User::make()->email('frodo@precious.com')->data(['name' => 'Frodo', 'entries_field' => [1, 2]])->save(); + + $users = User::query()->whereHas('entries_field', function ($subquery) { + $subquery->where('title', 'Post 2'); + }) + ->get(); + + $this->assertCount(2, $users); + $this->assertEquals(['Gandalf', 'Frodo'], $users->map->name->all()); + + $users = User::query()->whereDoesntHave('entries_field', function ($subquery) { + $subquery->where('title', 'Post 2'); + }) + ->get(); + + $this->assertCount(1, $users); + $this->assertEquals(['Smeagol'], $users->map->name->all()); + } + + /** @test **/ + public function users_are_found_using_where_relation() + { + $this->createDummyCollectionAndEntries(); + + $blueprint = Blueprint::makeFromFields(['entries_field' => ['type' => 'entries']]); + Blueprint::shouldReceive('find')->with('user')->andReturn($blueprint); + + User::make()->email('gandalf@precious.com')->data(['name' => 'Gandalf', 'entries_field' => [2, 1]])->save(); + User::make()->email('smeagol@precious.com')->data(['name' => 'Smeagol'])->save(); + User::make()->email('frodo@precious.com')->data(['name' => 'Frodo', 'entries_field' => [1, 2]])->save(); + + $users = User::query()->whereRelation('entries_field', 'title', 'Post 2')->get(); + + $this->assertCount(2, $users); + $this->assertEquals(['Gandalf', 'Frodo'], $users->map->name->all()); + } + + private function createDummyCollectionAndEntries() + { + Collection::make('posts')->save(); + + EntryFactory::id('1')->slug('post-1')->collection('posts')->data(['title' => 'Post 1', 'author' => 'John Doe'])->create(); + $entry = EntryFactory::id('2')->slug('post-2')->collection('posts')->data(['title' => 'Post 2', 'author' => 'John Doe'])->create(); + EntryFactory::id('3')->slug('post-3')->collection('posts')->data(['title' => 'Post 3', 'author' => 'John Doe'])->create(); + + return $entry; + } } From 1913bd3976c5ebf2675f200b71589f6d4a4cfc7e Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Fri, 21 Jul 2023 07:25:02 +0100 Subject: [PATCH 06/15] Support basic relation check when maxItems: 1 --- src/Query/Traits/QueriesRelationships.php | 16 ++++++++++++---- tests/Data/Entries/EntryQueryBuilderTest.php | 10 ++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/Query/Traits/QueriesRelationships.php b/src/Query/Traits/QueriesRelationships.php index 5a674a2dfe..ef2041dedf 100644 --- a/src/Query/Traits/QueriesRelationships.php +++ b/src/Query/Traits/QueriesRelationships.php @@ -23,7 +23,19 @@ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', C { [$relationQueryBuilder, $relationField] = $this->getRelationQueryBuilderAndField($relation); + $maxItems = $relationField->config()['max_items'] ?? 0; + $negate = in_array($operator, ['!=', '<']); + if (! $callback) { + if ($maxItems == 1) { + $method = $boolean == 'and' ? 'whereNull' : 'orWhereNull'; + if (! $negate) { + $method = str_replace('Null', 'NotNull', $method); + } + + return $this->$method($relation); + } + return $this->{$boolean == 'and' ? 'whereJsonLength' : 'orWhereJsonLength'}($relation, $operator, $count, $boolean); } @@ -33,10 +45,6 @@ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', C ->map(fn ($item) => $item->id()) ->all(); - $maxItems = $relationField->config()['max_items'] ?? 0; - - $negate = in_array($operator, ['!=', '<']); - if ($count != 1) { throw new InvalidArgumentException("Counting with callbacks in has clauses is not supported"); } diff --git a/tests/Data/Entries/EntryQueryBuilderTest.php b/tests/Data/Entries/EntryQueryBuilderTest.php index 0a0dbe11e2..92f8a912f8 100644 --- a/tests/Data/Entries/EntryQueryBuilderTest.php +++ b/tests/Data/Entries/EntryQueryBuilderTest.php @@ -685,6 +685,11 @@ public function entries_are_found_using_where_has_when_max_items_1() ]) ->save(); + $entries = Entry::query()->whereHas('entries_field')->get(); + + $this->assertCount(2, $entries); + $this->assertEquals(['Post 1', 'Post 3'], $entries->map->title->all()); + $entries = Entry::query()->whereHas('entries_field', function ($subquery) { $subquery->where('title', 'Post 2'); }) @@ -722,6 +727,11 @@ public function entries_are_found_using_where_has_when_max_items_not_1() ]) ->save(); + $entries = Entry::query()->whereHas('entries_field')->get(); + + $this->assertCount(2, $entries); + $this->assertEquals(['Post 1', 'Post 3'], $entries->map->title->all()); + $entries = Entry::query()->whereHas('entries_field', function ($subquery) { $subquery->where('title', 'Post 2'); }) From b8cba41f01bee1cdfc3e4b18993b8db8050a4205 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Fri, 21 Jul 2023 07:37:36 +0100 Subject: [PATCH 07/15] :beer: --- src/Fieldtypes/Entries.php | 2 +- src/Query/Traits/QueriesRelationships.php | 8 +------- tests/Data/Entries/EntryQueryBuilderTest.php | 12 ++++++------ tests/Data/Users/UserQueryBuilderTest.php | 1 - 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/Fieldtypes/Entries.php b/src/Fieldtypes/Entries.php index 6b2eb98730..97ad86dd7f 100644 --- a/src/Fieldtypes/Entries.php +++ b/src/Fieldtypes/Entries.php @@ -358,6 +358,6 @@ public function relationshipQueryBuilder() $collections = $this->config('collections'); return Entry::query() - ->when($collections, fn ($query) => $query->whereIn('collection', $collections)); + ->when($collections, fn ($query) => $query->whereIn('collection', $collections)); } } diff --git a/src/Query/Traits/QueriesRelationships.php b/src/Query/Traits/QueriesRelationships.php index ef2041dedf..7336c826e7 100644 --- a/src/Query/Traits/QueriesRelationships.php +++ b/src/Query/Traits/QueriesRelationships.php @@ -14,7 +14,6 @@ trait QueriesRelationships * @param string $operator * @param int $count * @param string $boolean - * @param \Closure|null $callback * @return \Statamic\Query\Builder|static * * @throws \InvalidArgumentException @@ -46,7 +45,7 @@ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', C ->all(); if ($count != 1) { - throw new InvalidArgumentException("Counting with callbacks in has clauses is not supported"); + throw new InvalidArgumentException('Counting with callbacks in has clauses is not supported'); } if ($maxItems == 1) { @@ -92,7 +91,6 @@ public function orHas($relation, $operator = '>=', $count = 1) * * @param string $relation * @param string $boolean - * @param \Closure|null $callback * @return \Statamic\Query\Builder|static */ public function doesntHave($relation, $boolean = 'and', Closure $callback = null) @@ -115,7 +113,6 @@ public function orDoesntHave($relation) * Add a relationship count / exists condition to the query with where clauses. * * @param string $relation - * @param \Closure|null $callback * @param string $operator * @param int $count * @return \Statamic\Query\Builder|static @@ -129,7 +126,6 @@ public function whereHas($relation, Closure $callback = null, $operator = '>=', * Add a relationship count / exists condition to the query with where clauses and an "or". * * @param string $relation - * @param \Closure|null $callback * @param string $operator * @param int $count * @return \Statamic\Query\Builder|static @@ -143,7 +139,6 @@ public function orWhereHas($relation, Closure $callback = null, $operator = '>=' * Add a relationship count / exists condition to the query with where clauses. * * @param string $relation - * @param \Closure|null $callback * @return \Statamic\Query\Builder|static */ public function whereDoesntHave($relation, Closure $callback = null) @@ -155,7 +150,6 @@ public function whereDoesntHave($relation, Closure $callback = null) * Add a relationship count / exists condition to the query with where clauses and an "or". * * @param string $relation - * @param \Closure|null $callback * @return \Statamic\Query\Builder|static */ public function orWhereDoesntHave($relation, Closure $callback = null) diff --git a/tests/Data/Entries/EntryQueryBuilderTest.php b/tests/Data/Entries/EntryQueryBuilderTest.php index 92f8a912f8..91346fec9f 100644 --- a/tests/Data/Entries/EntryQueryBuilderTest.php +++ b/tests/Data/Entries/EntryQueryBuilderTest.php @@ -675,13 +675,13 @@ public function entries_are_found_using_where_has_when_max_items_1() Entry::find(1) ->merge([ - 'entries_field' => 2 + 'entries_field' => 2, ]) ->save(); Entry::find(3) ->merge([ - 'entries_field' => 1 + 'entries_field' => 1, ]) ->save(); @@ -717,13 +717,13 @@ public function entries_are_found_using_where_has_when_max_items_not_1() Entry::find(1) ->merge([ - 'entries_field' => [2, 1] + 'entries_field' => [2, 1], ]) ->save(); Entry::find(3) ->merge([ - 'entries_field' => [1, 2] + 'entries_field' => [1, 2], ]) ->save(); @@ -759,13 +759,13 @@ public function entries_are_found_using_where_relation() Entry::find(1) ->merge([ - 'entries_field' => [2, 1] + 'entries_field' => [2, 1], ]) ->save(); Entry::find(3) ->merge([ - 'entries_field' => [1, 2] + 'entries_field' => [1, 2], ]) ->save(); diff --git a/tests/Data/Users/UserQueryBuilderTest.php b/tests/Data/Users/UserQueryBuilderTest.php index c76585694b..32ae45082b 100644 --- a/tests/Data/Users/UserQueryBuilderTest.php +++ b/tests/Data/Users/UserQueryBuilderTest.php @@ -5,7 +5,6 @@ use Facades\Tests\Factories\EntryFactory; use Statamic\Facades\Blueprint; use Statamic\Facades\Collection; -use Statamic\Facades\Entry; use Statamic\Facades\User; use Tests\PreventSavingStacheItemsToDisk; use Tests\TestCase; From e6b82b614d6a8ce15002a242b3c2b861f3a06db2 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Fri, 21 Jul 2023 10:33:29 +0100 Subject: [PATCH 08/15] Move count check and rewrite error message --- src/Query/Traits/QueriesRelationships.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Query/Traits/QueriesRelationships.php b/src/Query/Traits/QueriesRelationships.php index 7336c826e7..eb7cfb4473 100644 --- a/src/Query/Traits/QueriesRelationships.php +++ b/src/Query/Traits/QueriesRelationships.php @@ -38,16 +38,16 @@ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', C return $this->{$boolean == 'and' ? 'whereJsonLength' : 'orWhereJsonLength'}($relation, $operator, $count, $boolean); } + if ($count != 1) { + throw new InvalidArgumentException('Counting with subqueries in has clauses is not supported'); + } + $ids = $relationQueryBuilder ->where($callback) ->get(['id']) ->map(fn ($item) => $item->id()) ->all(); - if ($count != 1) { - throw new InvalidArgumentException('Counting with callbacks in has clauses is not supported'); - } - if ($maxItems == 1) { $method = $boolean == 'and' ? 'whereIn' : 'orWhereIn'; if ($negate) { From 86c1dcc570768f589eedfe92da9d8b885eb08f69 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Tue, 8 Aug 2023 13:54:13 +0100 Subject: [PATCH 09/15] Don't need boolean here --- src/Query/Traits/QueriesRelationships.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Query/Traits/QueriesRelationships.php b/src/Query/Traits/QueriesRelationships.php index eb7cfb4473..fecd3b5231 100644 --- a/src/Query/Traits/QueriesRelationships.php +++ b/src/Query/Traits/QueriesRelationships.php @@ -35,7 +35,7 @@ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', C return $this->$method($relation); } - return $this->{$boolean == 'and' ? 'whereJsonLength' : 'orWhereJsonLength'}($relation, $operator, $count, $boolean); + return $this->{$boolean == 'and' ? 'whereJsonLength' : 'orWhereJsonLength'}($relation, $operator, $count); } if ($count != 1) { From 39860564c92c7d472d1f1bfcc3c89a2599a8b3e4 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Thu, 5 Oct 2023 09:49:01 +0100 Subject: [PATCH 10/15] Support taxonomies for @robdekort --- src/Fieldtypes/Terms.php | 8 ++ src/Query/Traits/QueriesRelationships.php | 3 +- src/Stache/Query/TermQueryBuilder.php | 17 ++++ .../Data/Taxonomies/TermQueryBuilderTest.php | 83 +++++++++++++++++++ 4 files changed, 110 insertions(+), 1 deletion(-) diff --git a/src/Fieldtypes/Terms.php b/src/Fieldtypes/Terms.php index 0b75a98421..fa13efbbdd 100644 --- a/src/Fieldtypes/Terms.php +++ b/src/Fieldtypes/Terms.php @@ -446,4 +446,12 @@ protected function getItemsForPreProcessIndex($values): Collection return $this->config('max_items') === 1 ? collect([$augmented]) : $augmented->get(); } + + public function relationshipQueryBuilder() + { + $taxonomies = $this->taxonomies(); + + return Term::query() + ->when($taxonomies, fn ($query) => $query->whereIn('taxonomy', $taxonomies)); + } } diff --git a/src/Query/Traits/QueriesRelationships.php b/src/Query/Traits/QueriesRelationships.php index fecd3b5231..6b4765c736 100644 --- a/src/Query/Traits/QueriesRelationships.php +++ b/src/Query/Traits/QueriesRelationships.php @@ -4,6 +4,7 @@ use Closure; use InvalidArgumentException; +use Statamic\Support\Str; trait QueriesRelationships { @@ -45,7 +46,7 @@ public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', C $ids = $relationQueryBuilder ->where($callback) ->get(['id']) - ->map(fn ($item) => $item->id()) + ->map(fn ($item) => Str::after($item->id(), '::')) ->all(); if ($maxItems == 1) { diff --git a/src/Stache/Query/TermQueryBuilder.php b/src/Stache/Query/TermQueryBuilder.php index 3dca2d45cf..386e23a005 100644 --- a/src/Stache/Query/TermQueryBuilder.php +++ b/src/Stache/Query/TermQueryBuilder.php @@ -176,4 +176,21 @@ protected function getWhereColumnKeyValuesByIndex($column) return $items; } + + protected function getBlueprintsForRelations() + { + $taxonomies = empty($this->taxonomies) + ? Facades\Taxonomy::handles() + : $this->taxonomies; + + return $taxonomies->flatMap(function ($taxonomy) { + if (is_string($taxonomy)) { + $taxonomy = Facades\Taxonomy::find($taxonomy); + } + + return $taxonomy ? $taxonomy->termBlueprints() : false; + }) + ->filter() + ->unique(); + } } diff --git a/tests/Data/Taxonomies/TermQueryBuilderTest.php b/tests/Data/Taxonomies/TermQueryBuilderTest.php index f44a4e556d..007bd25827 100644 --- a/tests/Data/Taxonomies/TermQueryBuilderTest.php +++ b/tests/Data/Taxonomies/TermQueryBuilderTest.php @@ -598,4 +598,87 @@ public function terms_are_found_using_offset() $terms = Term::query()->offset(1)->get(); $this->assertEquals(['b', 'c'], $terms->map->slug()->all()); } + + /** @test **/ + public function terms_are_found_using_where_has_when_max_items_1() + { + $blueprint = Blueprint::makeFromFields(['terms_field' => ['type' => 'terms', 'max_items' => 1]]); + Blueprint::shouldReceive('in')->with('taxonomies/tags')->andReturn(collect(['tags' => $blueprint])); + + Taxonomy::make('tags')->save(); + Term::make('a')->taxonomy('tags')->data([])->save(); + Term::make('b')->taxonomy('tags')->data(['terms_field' => 'a'])->save(); + Term::make('c')->taxonomy('tags')->data(['terms_field' => 'b'])->save(); + + $terms = Term::query()->whereHas('terms_field')->get(); + + $this->assertCount(2, $terms); + $this->assertEquals(['b', 'c'], $terms->map->slug->all()); + + $terms = Term::query()->whereHas('terms_field', function ($subquery) { + $subquery->where('title', 'a'); + }) + ->get(); + + $this->assertCount(1, $terms); + $this->assertEquals(['b'], $terms->map->slug->all()); + + $terms = Term::query()->whereDoesntHave('terms_field', function ($subquery) { + $subquery->where('title', 'a'); + }) + ->get(); + + $this->assertCount(2, $terms); + $this->assertEquals(['a', 'c'], $terms->map->slug->all()); + } + + /** @test **/ + public function terms_are_found_using_where_has_when_max_items_not_1() + { + $blueprint = Blueprint::makeFromFields(['terms_field' => ['type' => 'terms', 'max_items' => 1]]); + Blueprint::shouldReceive('in')->with('taxonomies/tags')->andReturn(collect(['tags' => $blueprint])); + + Taxonomy::make('tags')->save(); + Term::make('a')->taxonomy('tags')->data([])->save(); + Term::make('b')->taxonomy('tags')->data(['terms_field' => ['a', 'c']])->save(); + Term::make('c')->taxonomy('tags')->data(['terms_field' => ['b', 'a']])->save(); + + $terms = Term::query()->whereHas('terms_field')->get(); + + $this->assertCount(2, $terms); + $this->assertEquals(['b', 'c'], $terms->map->slug->all()); + + $terms = Term::query()->whereHas('terms_field', function ($subquery) { + $subquery->where('slug', 'b'); + }) + ->get(); + + $this->assertCount(1, $terms); + $this->assertEquals(['c'], $terms->map->slug->all()); + + $terms = Term::query()->whereDoesntHave('terms_field', function ($subquery) { + $subquery->where('title', 'b'); + }) + ->get(); + + $this->assertCount(2, $terms); + $this->assertEquals(['a', 'b'], $terms->map->slug->all()); + } + + /** @test **/ + public function terms_are_found_using_where_relation() + { + $blueprint = Blueprint::makeFromFields(['terms_field' => ['type' => 'terms', 'max_items' => 1]]); + Blueprint::shouldReceive('in')->with('taxonomies/tags')->andReturn(collect(['tags' => $blueprint])); + + Taxonomy::make('tags')->save(); + Term::make('a')->taxonomy('tags')->data([])->save(); + Term::make('b')->taxonomy('tags')->data(['terms_field' => ['a', 'c']])->save(); + Term::make('c')->taxonomy('tags')->data(['terms_field' => ['b', 'a']])->save(); + + $terms = Term::query()->whereRelation('terms_field', 'slug', 'b')->get(); + + $this->assertCount(1, $terms); + $this->assertEquals(['c'], $terms->map->slug->all()); + } } From 3c948357a917bed15c32809c5ec7a4cd369eaaf5 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Thu, 5 Oct 2023 15:20:50 +0100 Subject: [PATCH 11/15] Tidy up --- src/Stache/Query/EntryQueryBuilder.php | 16 ++++------------ src/Stache/Query/TermQueryBuilder.php | 2 +- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/Stache/Query/EntryQueryBuilder.php b/src/Stache/Query/EntryQueryBuilder.php index 13901b27c0..6cf91fa491 100644 --- a/src/Stache/Query/EntryQueryBuilder.php +++ b/src/Stache/Query/EntryQueryBuilder.php @@ -134,19 +134,11 @@ protected function getWhereColumnKeyValuesByIndex($column) protected function getBlueprintsForRelations() { - $wheres = collect($this->wheres); - - $collections = $wheres->where('column', 'collection') - ->flatMap(function ($where) { - return $where['values'] ?? [$where['value']] ?? []; - }) - ->unique(); - - if (! $collections->count()) { - $collections = Facades\Collection::all(); - } + $collections = empty($this->collections) + ? Facades\Collection::all() + : $this->collections; - return $collections->flatMap(function ($collection) { + return collect($collections)->flatMap(function ($collection) { if (is_string($collection)) { $collection = Facades\Collection::find($collection); } diff --git a/src/Stache/Query/TermQueryBuilder.php b/src/Stache/Query/TermQueryBuilder.php index 386e23a005..e3f64efdb6 100644 --- a/src/Stache/Query/TermQueryBuilder.php +++ b/src/Stache/Query/TermQueryBuilder.php @@ -183,7 +183,7 @@ protected function getBlueprintsForRelations() ? Facades\Taxonomy::handles() : $this->taxonomies; - return $taxonomies->flatMap(function ($taxonomy) { + return collect($taxonomies)->flatMap(function ($taxonomy) { if (is_string($taxonomy)) { $taxonomy = Facades\Taxonomy::find($taxonomy); } From 3e81f42e46ea61db8df1296eb477f3d63b7fa97e Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Tue, 12 Dec 2023 10:36:39 +0000 Subject: [PATCH 12/15] :beer: --- src/Query/Traits/QueriesRelationships.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Query/Traits/QueriesRelationships.php b/src/Query/Traits/QueriesRelationships.php index 6b4765c736..91683b8d8c 100644 --- a/src/Query/Traits/QueriesRelationships.php +++ b/src/Query/Traits/QueriesRelationships.php @@ -19,7 +19,7 @@ trait QueriesRelationships * * @throws \InvalidArgumentException */ - public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null) + public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', ?Closure $callback = null) { [$relationQueryBuilder, $relationField] = $this->getRelationQueryBuilderAndField($relation); @@ -94,7 +94,7 @@ public function orHas($relation, $operator = '>=', $count = 1) * @param string $boolean * @return \Statamic\Query\Builder|static */ - public function doesntHave($relation, $boolean = 'and', Closure $callback = null) + public function doesntHave($relation, $boolean = 'and', ?Closure $callback = null) { return $this->has($relation, '<', 1, $boolean, $callback); } @@ -118,7 +118,7 @@ public function orDoesntHave($relation) * @param int $count * @return \Statamic\Query\Builder|static */ - public function whereHas($relation, Closure $callback = null, $operator = '>=', $count = 1) + public function whereHas($relation, ?Closure $callback = null, $operator = '>=', $count = 1) { return $this->has($relation, $operator, $count, 'and', $callback); } @@ -131,7 +131,7 @@ public function whereHas($relation, Closure $callback = null, $operator = '>=', * @param int $count * @return \Statamic\Query\Builder|static */ - public function orWhereHas($relation, Closure $callback = null, $operator = '>=', $count = 1) + public function orWhereHas($relation, ?Closure $callback = null, $operator = '>=', $count = 1) { return $this->has($relation, $operator, $count, 'or', $callback); } @@ -142,7 +142,7 @@ public function orWhereHas($relation, Closure $callback = null, $operator = '>=' * @param string $relation * @return \Statamic\Query\Builder|static */ - public function whereDoesntHave($relation, Closure $callback = null) + public function whereDoesntHave($relation, ?Closure $callback = null) { return $this->doesntHave($relation, 'and', $callback); } @@ -153,7 +153,7 @@ public function whereDoesntHave($relation, Closure $callback = null) * @param string $relation * @return \Statamic\Query\Builder|static */ - public function orWhereDoesntHave($relation, Closure $callback = null) + public function orWhereDoesntHave($relation, ?Closure $callback = null) { return $this->doesntHave($relation, 'or', $callback); } From 2167bc09601b4feecdfe770015731d1f5fd9753f Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Thu, 29 Feb 2024 10:16:35 +0000 Subject: [PATCH 13/15] :beer: --- tests/Data/Users/UserQueryBuilderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Data/Users/UserQueryBuilderTest.php b/tests/Data/Users/UserQueryBuilderTest.php index 6e188ae1b4..3d61250194 100644 --- a/tests/Data/Users/UserQueryBuilderTest.php +++ b/tests/Data/Users/UserQueryBuilderTest.php @@ -300,7 +300,7 @@ private function createDummyCollectionAndEntries() return $entry; } - + /** @test **/ public function users_are_found_using_where_group() { From 0d39b655622f27ccec74a4e8526d9c594bc4c78e Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Fri, 10 May 2024 11:28:50 +0100 Subject: [PATCH 14/15] ids have changed in tests --- tests/Data/Entries/EntryQueryBuilderTest.php | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/Data/Entries/EntryQueryBuilderTest.php b/tests/Data/Entries/EntryQueryBuilderTest.php index 268b2ad69d..cace7ad260 100644 --- a/tests/Data/Entries/EntryQueryBuilderTest.php +++ b/tests/Data/Entries/EntryQueryBuilderTest.php @@ -687,15 +687,15 @@ public function entries_are_found_using_where_has_when_max_items_1() $this->createDummyCollectionAndEntries(); - Entry::find(1) + Entry::find('id-1') ->merge([ - 'entries_field' => 2, + 'entries_field' => 'id-2', ]) ->save(); - Entry::find(3) + Entry::find('id-3') ->merge([ - 'entries_field' => 1, + 'entries_field' => 'id-1', ]) ->save(); @@ -729,15 +729,15 @@ public function entries_are_found_using_where_has_when_max_items_not_1() $this->createDummyCollectionAndEntries(); - Entry::find(1) + Entry::find('id-1') ->merge([ - 'entries_field' => [2, 1], + 'entries_field' => ['id-2', 'id-1'], ]) ->save(); - Entry::find(3) + Entry::find('id-3') ->merge([ - 'entries_field' => [1, 2], + 'entries_field' => ['id-1', 'id-2'], ]) ->save(); @@ -771,15 +771,15 @@ public function entries_are_found_using_where_relation() $this->createDummyCollectionAndEntries(); - Entry::find(1) + Entry::find('id-1') ->merge([ - 'entries_field' => [2, 1], + 'entries_field' => ['id-2', 'id-1'], ]) ->save(); - Entry::find(3) + Entry::find('id-3') ->merge([ - 'entries_field' => [1, 2], + 'entries_field' => ['id-1', 'id-2'], ]) ->save(); From 0f55edc366341faada9bce28f303a507193a2b4d Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Tue, 23 Jul 2024 09:28:39 +0100 Subject: [PATCH 15/15] :beer: --- src/Fieldtypes/Terms.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fieldtypes/Terms.php b/src/Fieldtypes/Terms.php index 0e6f94b2c7..64134f9ad9 100644 --- a/src/Fieldtypes/Terms.php +++ b/src/Fieldtypes/Terms.php @@ -487,7 +487,7 @@ public function relationshipQueryBuilder() return Term::query() ->when($taxonomies, fn ($query) => $query->whereIn('taxonomy', $taxonomies)); } - + public function getItemHint($item): ?string { return collect([