Skip to content

Commit c139586

Browse files
antograssiotdunglas
authored andcommitted
Allow GraphQL to filter on nested property (#1868)
* Allow GraphQL to filter on nested property fixes #1714, #1867 * Allow ordering on nested property values
1 parent 2fe4544 commit c139586

File tree

4 files changed

+121
-1
lines changed

4 files changed

+121
-1
lines changed

features/doctrine/order_filter.feature

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,3 +830,55 @@ Feature: Order filter on collections
830830
}
831831
}
832832
"""
833+
834+
@createSchema
835+
Scenario: Get collection ordered in descending order on a related property
836+
Given there are 2 dummy objects with relatedDummy
837+
When I send a "GET" request to "/dummies?order[relatedDummy.name]=desc"
838+
Then the response status code should be 200
839+
And the response should be in JSON
840+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
841+
And the JSON should be valid according to this schema:
842+
"""
843+
{
844+
"type": "object",
845+
"properties": {
846+
"@context": {"pattern": "^/contexts/Dummy$"},
847+
"@id": {"pattern": "^/dummies$"},
848+
"@type": {"pattern": "^hydra:Collection$"},
849+
"hydra:member": {
850+
"type": "array",
851+
"items": [
852+
{
853+
"type": "object",
854+
"properties": {
855+
"@id": {
856+
"type": "string",
857+
"pattern": "^/dummies/2$"
858+
}
859+
}
860+
},
861+
{
862+
"type": "object",
863+
"properties": {
864+
"@id": {
865+
"type": "string",
866+
"pattern": "^/dummies/1$"
867+
}
868+
}
869+
}
870+
],
871+
"additionalItems": false,
872+
"maxItems": 2,
873+
"minItems": 2
874+
},
875+
"hydra:view": {
876+
"type": "object",
877+
"properties": {
878+
"@id": {"pattern": "^/dummies\\?order%5BrelatedDummy.name%5D=desc"},
879+
"@type": {"pattern": "^hydra:PartialCollectionView$"}
880+
}
881+
}
882+
}
883+
}
884+
"""

features/graphql/filters.feature

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,46 @@ Feature: Collections filtering
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"
114+
115+
@createSchema
116+
Scenario: Retrieve a collection filtered using the related search filter
117+
Given there are 1 dummy objects having each 2 relatedDummies
118+
And there are 1 dummy objects having each 3 relatedDummies
119+
When I send the following GraphQL request:
120+
"""
121+
{
122+
dummies(relatedDummies_name: "RelatedDummy31") {
123+
edges {
124+
node {
125+
id
126+
}
127+
}
128+
}
129+
}
130+
"""
131+
And the response status code should be 200
132+
And the JSON node "data.dummies.edges" should have 1 element
133+
134+
@createSchema
135+
Scenario: Retrieve a collection ordered using nested properties
136+
Given there are 2 dummy objects with relatedDummy
137+
When I send the following GraphQL request:
138+
"""
139+
{
140+
dummies(order: {relatedDummy_name: "DESC"}) {
141+
edges {
142+
node {
143+
name
144+
relatedDummy {
145+
id
146+
name
147+
}
148+
}
149+
}
150+
}
151+
}
152+
"""
153+
Then the response status code should be 200
154+
And the header "Content-Type" should be equal to "application/json"
155+
And the JSON node "data.dummies.edges[0].node.name" should be equal to "Dummy #2"
156+
And the JSON node "data.dummies.edges[1].node.name" should be equal to "Dummy #1"

src/GraphQl/Resolver/Factory/CollectionResolverFactory.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public function __invoke(string $resourceClass = null, string $rootClass = null,
7777
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
7878
$dataProviderContext = $resourceMetadata->getGraphqlAttribute('query', 'normalization_context', [], true);
7979
$dataProviderContext['attributes'] = $this->fieldsToAttributes($info);
80-
$dataProviderContext['filters'] = $args;
80+
$dataProviderContext['filters'] = $this->getNormalizedFilters($args);
8181

8282
if (isset($rootClass, $source[$rootProperty = $info->fieldName], $source[ItemNormalizer::ITEM_KEY])) {
8383
$rootResolvedFields = $this->identifiersExtractor->getIdentifiersFromItem(unserialize($source[ItemNormalizer::ITEM_KEY]));
@@ -164,4 +164,22 @@ private function getSubresource(string $rootClass, array $rootResolvedFields, ar
164164
'collection' => $isCollection,
165165
]);
166166
}
167+
168+
private function getNormalizedFilters(array $args): array
169+
{
170+
$filters = $args;
171+
foreach ($filters as $name => $value) {
172+
if (\is_array($value)) {
173+
$filters[$name] = $this->getNormalizedFilters($value);
174+
continue;
175+
}
176+
177+
if (strpos($name, '_')) {
178+
// Gives a chance to relations/nested fields.
179+
$filters[str_replace('_', '.', $name)] = $value;
180+
}
181+
}
182+
183+
return $filters;
184+
}
167185
}

src/GraphQl/Type/SchemaBuilder.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,13 @@ private function mergeFilterArgs(array $args, array $parsed, ResourceMetadata $r
291291

292292
private function convertFilterArgsToTypes(array $args): array
293293
{
294+
foreach ($args as $key => $value) {
295+
if (strpos($key, '.')) {
296+
// Declare relations/nested fields in a GraphQL compatible syntax.
297+
$args[str_replace('.', '_', $key)] = $value;
298+
}
299+
}
300+
294301
foreach ($args as $key => $value) {
295302
if (!\is_array($value) || !isset($value['#name'])) {
296303
continue;

0 commit comments

Comments
 (0)