Skip to content

Commit f1802ea

Browse files
authored
Use correct resource configuration for filter args of nested collection (#2976)
1 parent df359cd commit f1802ea

File tree

5 files changed

+84
-43
lines changed

5 files changed

+84
-43
lines changed

features/graphql/filters.feature

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,33 @@ Feature: Collections filtering
107107
}
108108
}
109109
"""
110-
And the JSON node "data.dummies.edges[0].node.relatedDummies.edges" should have 0 elements
110+
Then the JSON node "data.dummies.edges[0].node.relatedDummies.edges" should have 0 elements
111111
And the JSON node "data.dummies.edges[1].node.relatedDummies.edges" should have 0 elements
112112
And the JSON node "data.dummies.edges[2].node.relatedDummies.edges" should have 1 element
113113
And the JSON node "data.dummies.edges[2].node.relatedDummies.edges[0].node.name" should be equal to "RelatedDummy13"
114114

115+
@createSchema
116+
Scenario: Use a filter of a nested collection
117+
Given there is a DummyCar entity with related colors
118+
When I send the following GraphQL request:
119+
"""
120+
{
121+
dummyCar(id: "/dummy_cars/1") {
122+
id
123+
colors(prop: "blue") {
124+
edges {
125+
node {
126+
id
127+
prop
128+
}
129+
}
130+
}
131+
}
132+
}
133+
"""
134+
Then the JSON node "data.dummyCar.colors.edges" should have 1 element
135+
And the JSON node "data.dummyCar.colors.edges[0].node.prop" should be equal to "blue"
136+
115137
@createSchema
116138
Scenario: Retrieve a collection filtered using the related search filter
117139
Given there are 1 dummy objects having each 2 relatedDummies

src/GraphQl/Type/SchemaBuilder.php

Lines changed: 54 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -168,12 +168,12 @@ private function getQueryFields(string $resourceClass, ResourceMetadata $resourc
168168
$shortName = $resourceMetadata->getShortName();
169169
$deprecationReason = $resourceMetadata->getGraphqlAttribute('query', 'deprecation_reason', '', true);
170170

171-
if ($fieldConfiguration = $this->getResourceFieldConfiguration($resourceClass, $resourceMetadata, null, $deprecationReason, new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass), $resourceClass)) {
171+
if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, $deprecationReason, new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass), $resourceClass)) {
172172
$fieldConfiguration['args'] += ['id' => ['type' => GraphQLType::id()]];
173173
$queryFields[lcfirst($shortName)] = $fieldConfiguration;
174174
}
175175

176-
if ($fieldConfiguration = $this->getResourceFieldConfiguration($resourceClass, $resourceMetadata, null, $deprecationReason, new Type(Type::BUILTIN_TYPE_OBJECT, false, null, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, $resourceClass)), $resourceClass)) {
176+
if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, $deprecationReason, new Type(Type::BUILTIN_TYPE_OBJECT, false, null, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, $resourceClass)), $resourceClass)) {
177177
$queryFields[lcfirst(Inflector::pluralize($shortName))] = $fieldConfiguration;
178178
}
179179

@@ -189,8 +189,8 @@ private function getMutationFields(string $resourceClass, ResourceMetadata $reso
189189
$resourceType = new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass);
190190
$deprecationReason = $resourceMetadata->getGraphqlAttribute($mutationName, 'deprecation_reason', '', true);
191191

192-
if ($fieldConfiguration = $this->getResourceFieldConfiguration($resourceClass, $resourceMetadata, ucfirst("{$mutationName}s a $shortName."), $deprecationReason, $resourceType, $resourceClass, false, $mutationName)) {
193-
$fieldConfiguration['args'] += ['input' => $this->getResourceFieldConfiguration($resourceClass, $resourceMetadata, null, $deprecationReason, $resourceType, $resourceClass, true, $mutationName)];
192+
if ($fieldConfiguration = $this->getResourceFieldConfiguration(ucfirst("{$mutationName}s a $shortName."), $deprecationReason, $resourceType, $resourceClass, false, $mutationName)) {
193+
$fieldConfiguration['args'] += ['input' => $this->getResourceFieldConfiguration(null, $deprecationReason, $resourceType, $resourceClass, true, $mutationName)];
194194

195195
if (!$this->isCollection($resourceType)) {
196196
$itemMutationResolverFactory = $this->itemMutationResolverFactory;
@@ -206,19 +206,27 @@ private function getMutationFields(string $resourceClass, ResourceMetadata $reso
206206
*
207207
* @see http://webonyx.github.io/graphql-php/type-system/object-types/
208208
*/
209-
private function getResourceFieldConfiguration(string $resourceClass, ResourceMetadata $resourceMetadata, ?string $fieldDescription, string $deprecationReason, Type $type, string $rootResource, bool $input = false, string $mutationName = null, int $depth = 0): ?array
209+
private function getResourceFieldConfiguration(?string $fieldDescription, string $deprecationReason, Type $type, string $rootResource, bool $input = false, string $mutationName = null, int $depth = 0): ?array
210210
{
211211
try {
212+
$resourceClass = $this->isCollection($type) && ($collectionValueType = $type->getCollectionValueType()) ? $collectionValueType->getClassName() : $type->getClassName();
213+
212214
if (null === $graphqlType = $this->convertType($type, $input, $mutationName, $depth)) {
213215
return null;
214216
}
215217

216218
$graphqlWrappedType = $graphqlType instanceof WrappingType ? $graphqlType->getWrappedType() : $graphqlType;
217219
$isStandardGraphqlType = \in_array($graphqlWrappedType, GraphQLType::getStandardTypes(), true);
218220
if ($isStandardGraphqlType) {
219-
$className = '';
220-
} else {
221-
$className = $this->isCollection($type) && ($collectionValueType = $type->getCollectionValueType()) ? $collectionValueType->getClassName() : $type->getClassName();
221+
$resourceClass = '';
222+
}
223+
224+
$resourceMetadata = null;
225+
if (!empty($resourceClass)) {
226+
try {
227+
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
228+
} catch (ResourceClassNotFoundException $e) {
229+
}
222230
}
223231

224232
$args = [];
@@ -236,39 +244,14 @@ private function getResourceFieldConfiguration(string $resourceClass, ResourceMe
236244
];
237245
}
238246

239-
foreach ($resourceMetadata->getGraphqlAttribute('query', 'filters', [], true) as $filterId) {
240-
if (null === $this->filterLocator || !$this->filterLocator->has($filterId)) {
241-
continue;
242-
}
243-
244-
foreach ($this->filterLocator->get($filterId)->getDescription($resourceClass) as $key => $value) {
245-
$nullable = isset($value['required']) ? !$value['required'] : true;
246-
$filterType = \in_array($value['type'], Type::$builtinTypes, true) ? new Type($value['type'], $nullable) : new Type('object', $nullable, $value['type']);
247-
$graphqlFilterType = $this->convertType($filterType, false, null, $depth);
248-
249-
if ('[]' === substr($key, -2)) {
250-
$graphqlFilterType = GraphQLType::listOf($graphqlFilterType);
251-
$key = substr($key, 0, -2).'_list';
252-
}
253-
254-
parse_str($key, $parsed);
255-
if (\array_key_exists($key, $parsed) && \is_array($parsed[$key])) {
256-
$parsed = [$key => ''];
257-
}
258-
array_walk_recursive($parsed, function (&$value) use ($graphqlFilterType) {
259-
$value = $graphqlFilterType;
260-
});
261-
$args = $this->mergeFilterArgs($args, $parsed, $resourceMetadata, $key);
262-
}
263-
}
264-
$args = $this->convertFilterArgsToTypes($args);
247+
$args = $this->getFilterArgs($args, $resourceClass, $resourceMetadata, $depth);
265248
}
266249

267250
if ($isStandardGraphqlType || $input) {
268251
$resolve = null;
269252
} elseif ($this->isCollection($type)) {
270253
$resolverFactory = $this->collectionResolverFactory;
271-
$resolve = $resolverFactory($className, $rootResource, $mutationName);
254+
$resolve = $resolverFactory($resourceClass, $rootResource, $mutationName);
272255
} else {
273256
$resolve = $this->itemResolver;
274257
}
@@ -287,6 +270,41 @@ private function getResourceFieldConfiguration(string $resourceClass, ResourceMe
287270
return null;
288271
}
289272

273+
private function getFilterArgs(array $args, ?string $resourceClass, ?ResourceMetadata $resourceMetadata, int $depth): array
274+
{
275+
if (null === $resourceMetadata || null === $resourceClass) {
276+
return $args;
277+
}
278+
279+
foreach ($resourceMetadata->getGraphqlAttribute('query', 'filters', [], true) as $filterId) {
280+
if (null === $this->filterLocator || !$this->filterLocator->has($filterId)) {
281+
continue;
282+
}
283+
284+
foreach ($this->filterLocator->get($filterId)->getDescription($resourceClass) as $key => $value) {
285+
$nullable = isset($value['required']) ? !$value['required'] : true;
286+
$filterType = \in_array($value['type'], Type::$builtinTypes, true) ? new Type($value['type'], $nullable) : new Type('object', $nullable, $value['type']);
287+
$graphqlFilterType = $this->convertType($filterType, false, null, $depth);
288+
289+
if ('[]' === substr($key, -2)) {
290+
$graphqlFilterType = GraphQLType::listOf($graphqlFilterType);
291+
$key = substr($key, 0, -2).'_list';
292+
}
293+
294+
parse_str($key, $parsed);
295+
if (\array_key_exists($key, $parsed) && \is_array($parsed[$key])) {
296+
$parsed = [$key => ''];
297+
}
298+
array_walk_recursive($parsed, function (&$value) use ($graphqlFilterType) {
299+
$value = $graphqlFilterType;
300+
});
301+
$args = $this->mergeFilterArgs($args, $parsed, $resourceMetadata, $key);
302+
}
303+
}
304+
305+
return $this->convertFilterArgsToTypes($args);
306+
}
307+
290308
private function mergeFilterArgs(array $args, array $parsed, ResourceMetadata $resourceMetadata = null, $original = ''): array
291309
{
292310
foreach ($parsed as $key => $value) {
@@ -508,15 +526,9 @@ private function getResourceObjectTypeFields(?string $resourceClass, ResourceMet
508526
continue;
509527
}
510528

511-
$rootResource = $resourceClass;
512-
if (null !== $propertyMetadata->getSubresource()) {
513-
$resourceClass = $propertyMetadata->getSubresource()->getResourceClass();
514-
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
515-
}
516-
if ($fieldConfiguration = $this->getResourceFieldConfiguration($resourceClass, $resourceMetadata, $propertyMetadata->getDescription(), $propertyMetadata->getAttribute('deprecation_reason', ''), $propertyType, $rootResource, $input, $mutationName, $depth)) {
529+
if ($fieldConfiguration = $this->getResourceFieldConfiguration($propertyMetadata->getDescription(), $propertyMetadata->getAttribute('deprecation_reason', ''), $propertyType, $resourceClass, $input, $mutationName, $depth)) {
517530
$fields['id' === $property ? '_id' : $property] = $fieldConfiguration;
518531
}
519-
$resourceClass = $rootResource;
520532
}
521533
}
522534

tests/Fixtures/TestBundle/Document/DummyCarColor.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313

1414
namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Document;
1515

16+
use ApiPlatform\Core\Annotation\ApiFilter;
1617
use ApiPlatform\Core\Annotation\ApiResource;
18+
use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\SearchFilter;
1719
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
1820
use Symfony\Component\Serializer\Annotation as Serializer;
1921
use Symfony\Component\Validator\Constraints as Assert;
@@ -43,6 +45,7 @@ class DummyCarColor
4345
* @var string
4446
*
4547
* @ODM\Field(nullable=false)
48+
* @ApiFilter(SearchFilter::class)
4649
* @Assert\NotBlank
4750
*
4851
* @Serializer\Groups({"colors"})

tests/Fixtures/TestBundle/Entity/DummyCarColor.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313

1414
namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;
1515

16+
use ApiPlatform\Core\Annotation\ApiFilter;
1617
use ApiPlatform\Core\Annotation\ApiResource;
18+
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
1719
use Doctrine\ORM\Mapping as ORM;
1820
use Symfony\Component\Serializer\Annotation as Serializer;
1921
use Symfony\Component\Validator\Constraints as Assert;
@@ -46,6 +48,7 @@ class DummyCarColor
4648
* @var string
4749
*
4850
* @ORM\Column(nullable=false)
51+
* @ApiFilter(SearchFilter::class)
4952
* @Assert\NotBlank
5053
*
5154
* @Serializer\Groups({"colors"})

tests/GraphQl/Type/SchemaBuilderTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ private function createSchemaBuilder($propertyMetadataMockBuilder, bool $paginat
275275
);
276276
$resourceMetadataFactoryProphecy->create($resourceClassName)->willReturn($resourceMetadata);
277277
$resourceMetadataFactoryProphecy->create('unknownResource')->willThrow(new ResourceClassNotFoundException());
278+
$resourceMetadataFactoryProphecy->create('DateTime')->willThrow(new ResourceClassNotFoundException());
278279

279280
$propertyNames = [];
280281
foreach (Type::$builtinTypes as $builtinType) {

0 commit comments

Comments
 (0)