Skip to content

Commit 1ba2c24

Browse files
authored
Merge pull request #89 from TomHAnderson/feature/filters-by-field-type
Feature/filters by field type
2 parents 3960e7d + b9a156b commit 1ba2c24

File tree

4 files changed

+118
-30
lines changed

4 files changed

+118
-30
lines changed

src/Filter/FilterFactory.php

Lines changed: 102 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use ApiSkeletons\Doctrine\ORM\GraphQL\Filter\InputObjectType\Field;
1010
use ApiSkeletons\Doctrine\ORM\GraphQL\Type\Entity\Entity;
1111
use ApiSkeletons\Doctrine\ORM\GraphQL\Type\TypeManager;
12+
use Doctrine\Common\Collections\ArrayCollection;
1213
use Doctrine\ORM\EntityManager;
1314
use Doctrine\ORM\Mapping\ClassMetadata;
1415
use GraphQL\Type\Definition\InputObjectType as GraphQLInputObjectType;
@@ -118,10 +119,16 @@ protected function addFields(Entity $targetEntity, string $typeName, array $allo
118119
continue;
119120
}
120121

121-
$graphQLType = $this->typeManager
122+
$type = $this->typeManager
122123
->get($entityMetadata['fields'][$fieldName]['type']);
123124

124-
if (! $graphQLType instanceof ScalarType) {
125+
// Custom types may hit this condition
126+
if (! $type instanceof ScalarType) {
127+
continue;
128+
}
129+
130+
// Skip Blob fields
131+
if ($type->name() === 'Blob') {
125132
continue;
126133
}
127134

@@ -139,21 +146,24 @@ static function ($value) use ($fieldExcludeFilters) {
139146
);
140147
}
141148

149+
// Remove filters that are not allowed for this field type
150+
$filteredFilters = $this->filterFiltersByType($allowedFilters, $type);
151+
142152
// ScalarType field filters are named by their field type
143153
// and a hash of the allowed filters
144-
$filterTypeName = 'Filters_' . $graphQLType->name . '_' . md5(serialize($allowedFilters));
154+
$filterTypeName = 'Filters_' . $type->name() . '_' . md5(serialize($filteredFilters));
145155

146156
if ($this->typeManager->has($filterTypeName)) {
147-
$type = $this->typeManager->get($filterTypeName);
157+
$fieldType = $this->typeManager->get($filterTypeName);
148158
} else {
149-
$type = new Field($this->typeManager, $typeName, $fieldName, $graphQLType, $allowedFilters);
150-
$this->typeManager->set($filterTypeName, $type);
159+
$fieldType = new Field($this->typeManager, $type, $filteredFilters);
160+
$this->typeManager->set($filterTypeName, $fieldType);
151161
}
152162

153163
$fields[$fieldName] = [
154164
'name' => $fieldName,
155-
'type' => $type,
156-
'description' => 'Filters for ' . $fieldName,
165+
'type' => $fieldType,
166+
'description' => $type->name() . ' Filters',
157167
];
158168
}
159169

@@ -198,20 +208,99 @@ protected function addAssociations(Entity $targetEntity, string $typeName, array
198208
$filterTypeName = 'Filters_ID_' . md5(serialize($allowedFilters));
199209

200210
if ($this->typeManager->has($filterTypeName)) {
201-
$type = $this->typeManager->get($filterTypeName);
211+
$associationType = $this->typeManager->get($filterTypeName);
202212
} else {
203-
$type = new Association($this->typeManager, $typeName, $associationName, Type::id(), [Filters::EQ]);
204-
$this->typeManager->set($filterTypeName, $type);
213+
$associationType = new Association($this->typeManager, Type::id(), [Filters::EQ]);
214+
$this->typeManager->set($filterTypeName, $associationType);
205215
}
206216

207217
// eq filter is for association id from parent entity
208218
$fields[$associationName] = [
209219
'name' => $associationName,
210-
'type' => $type,
211-
'description' => 'Filters for ' . $associationName,
220+
'type' => $associationType,
221+
'description' => 'Association Filters',
212222
];
213223
}
214224

215225
return $fields;
216226
}
227+
228+
/**
229+
* Filter the allowed filters based on the field type
230+
*
231+
* @param Filters[] $filters
232+
*
233+
* @return Filters[]
234+
*/
235+
protected function filterFiltersByType(array $filters, ScalarType $type): array
236+
{
237+
$filterCollection = new ArrayCollection($filters);
238+
239+
// Numbers
240+
if (
241+
in_array($type->name(), [
242+
'Float',
243+
'ID',
244+
'Int',
245+
'Integer',
246+
])
247+
) {
248+
$filterCollection->removeElement(Filters::CONTAINS);
249+
$filterCollection->removeElement(Filters::STARTSWITH);
250+
$filterCollection->removeElement(Filters::ENDSWITH);
251+
252+
return $filterCollection->toArray();
253+
}
254+
255+
// Booleans
256+
if ($type->name() === 'Boolean') {
257+
$filterCollection->removeElement(Filters::LT);
258+
$filterCollection->removeElement(Filters::LTE);
259+
$filterCollection->removeElement(Filters::GT);
260+
$filterCollection->removeElement(Filters::GTE);
261+
$filterCollection->removeElement(Filters::BETWEEN);
262+
$filterCollection->removeElement(Filters::CONTAINS);
263+
$filterCollection->removeElement(Filters::STARTSWITH);
264+
$filterCollection->removeElement(Filters::ENDSWITH);
265+
266+
return $filterCollection->toArray();
267+
}
268+
269+
// Strings
270+
if (
271+
in_array($type->name(), [
272+
'String',
273+
'Text',
274+
])
275+
) {
276+
$filterCollection->removeElement(Filters::LT);
277+
$filterCollection->removeElement(Filters::LTE);
278+
$filterCollection->removeElement(Filters::GT);
279+
$filterCollection->removeElement(Filters::GTE);
280+
$filterCollection->removeElement(Filters::BETWEEN);
281+
282+
return $filterCollection->toArray();
283+
}
284+
285+
// Dates and times
286+
if (
287+
in_array($type->name(), [
288+
'Date',
289+
'DateTime',
290+
'DateTimeImmutable',
291+
'DateTimeTZ',
292+
'DateTimeTZImmutable',
293+
'Time',
294+
'TimeImmutable',
295+
])
296+
) {
297+
$filterCollection->removeElement(Filters::CONTAINS);
298+
$filterCollection->removeElement(Filters::STARTSWITH);
299+
$filterCollection->removeElement(Filters::ENDSWITH);
300+
301+
return $filterCollection->toArray();
302+
}
303+
304+
return $filterCollection->toArray();
305+
}
217306
}

src/Filter/InputObjectType/Field.php

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ class Field extends InputObjectType
2323
/** @param Filters[] $allowedFilters */
2424
public function __construct(
2525
TypeManager $typeManager,
26-
string $typeName,
27-
string $fieldName,
2826
ScalarType|ListOfType $type,
2927
array $allowedFilters,
3028
) {
@@ -38,30 +36,31 @@ public function __construct(
3836
'description' => $filter->description(),
3937
];
4038

39+
// Custom types may hit this condition
4140
// @codeCoverageIgnoreStart
4241
if (! $type instanceof ScalarType) {
4342
continue;
4443
}
4544

4645
// @codeCoverageIgnoreEnd
4746

47+
// Between is a special case filter.
48+
// To avoid creating a new Between type for each field,
49+
// check if the Between type exists and reuse it.
4850
if (! ($fields[$filter->value]['type'] instanceof Between)) {
4951
continue;
5052
}
5153

52-
// Between is a special case filter.
53-
// To avoid creating a new Between type for each field,
54-
// check if the Between type exists and reuse it.
55-
if ($typeManager->has('Between_' . $type->name)) {
56-
$fields[$filter->value]['type'] = $typeManager->get('Between_' . $type->name);
54+
if ($typeManager->has('Between_' . $type->name())) {
55+
$fields[$filter->value]['type'] = $typeManager->get('Between_' . $type->name());
5756
} else {
5857
$betweenType = new Between($type);
59-
$typeManager->set('Between_' . $type->name, $betweenType);
58+
$typeManager->set('Between_' . $type->name(), $betweenType);
6059
$fields[$filter->value]['type'] = $betweenType;
6160
}
6261
}
6362

64-
$typeName = $type instanceof ScalarType ? $type->name : uniqid();
63+
$typeName = $type instanceof ScalarType ? $type->name() : uniqid();
6564

6665
// ScalarType field filters are named by their field type
6766
// and a hash of the allowed filters

test/Feature/Filter/ConfigExcludeFiltersTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,28 +47,28 @@ public function testConfigExcludeFilters(): void
4747
$result = GraphQL::executeQuery($schema, $query);
4848

4949
foreach ($result->errors as $error) {
50-
$this->assertEquals('Field "eq" is not defined by type "Filters_String_b26f464f97a76891491eafa1573acb24".', $error->getMessage());
50+
$this->assertEquals('Field "eq" is not defined by type "Filters_String_0812311810b0ba1d34247150620b78b0".', $error->getMessage());
5151
}
5252

5353
$query = '{ artists (filter: { name: { neq: "Grateful Dead" } } ) { edges { node { name } } } }';
5454
$result = GraphQL::executeQuery($schema, $query);
5555

5656
foreach ($result->errors as $error) {
57-
$this->assertEquals('Field "neq" is not defined by type "Filters_String_b26f464f97a76891491eafa1573acb24".', $error->getMessage());
57+
$this->assertEquals('Field "neq" is not defined by type "Filters_String_0812311810b0ba1d34247150620b78b0".', $error->getMessage());
5858
}
5959

6060
$query = '{ artists { edges { node { performances ( filter: {venue: { neq: "test"} } ) { edges { node { venue } } } } } } }';
6161
$result = GraphQL::executeQuery($schema, $query);
6262

6363
foreach ($result->errors as $error) {
64-
$this->assertEquals('Field "neq" is not defined by type "Filters_String_b26f464f97a76891491eafa1573acb24".', $error->getMessage());
64+
$this->assertEquals('Field "neq" is not defined by type "Filters_String_0812311810b0ba1d34247150620b78b0".', $error->getMessage());
6565
}
6666

6767
$query = '{ artists { edges { node { performances ( filter: {venue: { contains: "test" } } ) { edges { node { venue } } } } } } }';
6868
$result = GraphQL::executeQuery($schema, $query);
6969

7070
foreach ($result->errors as $error) {
71-
$this->assertEquals('Field "contains" is not defined by type "Filters_String_b26f464f97a76891491eafa1573acb24". Did you mean "notin"?', $error->getMessage());
71+
$this->assertEquals('Field "contains" is not defined by type "Filters_String_0812311810b0ba1d34247150620b78b0". Did you mean "notin"?', $error->getMessage());
7272
}
7373
}
7474
}

test/Feature/Filter/ExcludeFiltersTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,28 +39,28 @@ public function testExcludeCriteria(): void
3939
$result = GraphQL::executeQuery($schema, $query);
4040

4141
foreach ($result->errors as $error) {
42-
$this->assertEquals('Field "eq" is not defined by type "Filters_String_436fb9911a1f07ad8eb7057c1a8e3d2b".', $error->getMessage());
42+
$this->assertEquals('Field "eq" is not defined by type "Filters_String_a03586330c4e7326edac556450d913ee".', $error->getMessage());
4343
}
4444

4545
$query = '{ artists (filter: { name: { neq: "Grateful Dead" } } ) { edges { node { name } } } }';
4646
$result = GraphQL::executeQuery($schema, $query);
4747

4848
foreach ($result->errors as $error) {
49-
$this->assertEquals('Field "neq" is not defined by type "Filters_String_436fb9911a1f07ad8eb7057c1a8e3d2b".', $error->getMessage());
49+
$this->assertEquals('Field "neq" is not defined by type "Filters_String_a03586330c4e7326edac556450d913ee".', $error->getMessage());
5050
}
5151

5252
$query = '{ artists { edges { node { performances ( filter: {venue: { neq: "test"} } ) { edges { node { venue } } } } } } }';
5353
$result = GraphQL::executeQuery($schema, $query);
5454

5555
foreach ($result->errors as $error) {
56-
$this->assertEquals('Field "neq" is not defined by type "Filters_String_bef569e688f8bb56acb1e0e4e430b055". Did you mean "eq"?', $error->getMessage());
56+
$this->assertEquals('Field "neq" is not defined by type "Filters_String_e55a7b533af3c46236f06d0fb99f08c6". Did you mean "eq"?', $error->getMessage());
5757
}
5858

5959
$query = '{ artists { edges { node { performances ( filter: {venue: { contains: "test" } } ) { edges { node { venue } } } } } } }';
6060
$result = GraphQL::executeQuery($schema, $query);
6161

6262
foreach ($result->errors as $error) {
63-
$this->assertEquals('Field "contains" is not defined by type "Filters_String_bef569e688f8bb56acb1e0e4e430b055". Did you mean "notin"?', $error->getMessage());
63+
$this->assertEquals('Field "contains" is not defined by type "Filters_String_e55a7b533af3c46236f06d0fb99f08c6". Did you mean "notin"?', $error->getMessage());
6464
}
6565
}
6666
}

0 commit comments

Comments
 (0)