Skip to content

Commit 7eec78c

Browse files
antograssiotteohhanhui
authored andcommitted
Allow GraphQL to filter on nested property (api-platform#1868)
* Allow GraphQL to filter on nested property fixes api-platform#1714, api-platform#1867 * Allow ordering on nested property values
1 parent 4bd2b82 commit 7eec78c

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

features/graphql/filters.feature

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,46 @@ Feature: Collections filtering
116116
And the JSON node "data.dummies.edges[1].node.relatedDummies.edges" should have 0 elements
117117
And the JSON node "data.dummies.edges[2].node.relatedDummies.edges" should have 1 element
118118
And the JSON node "data.dummies.edges[2].node.relatedDummies.edges[0].node.name" should be equal to "RelatedDummy13"
119+
120+
@createSchema
121+
Scenario: Retrieve a collection filtered using the related search filter
122+
Given there are 1 dummy objects having each 2 relatedDummies
123+
And there are 1 dummy objects having each 3 relatedDummies
124+
When I send the following GraphQL request:
125+
"""
126+
{
127+
dummies(relatedDummies_name: "RelatedDummy31") {
128+
edges {
129+
node {
130+
id
131+
}
132+
}
133+
}
134+
}
135+
"""
136+
And the response status code should be 200
137+
And the JSON node "data.dummies.edges" should have 1 element
138+
139+
@createSchema
140+
Scenario: Retrieve a collection ordered using nested properties
141+
Given there are 2 dummy objects with relatedDummy
142+
When I send the following GraphQL request:
143+
"""
144+
{
145+
dummies(order: {relatedDummy_name: "DESC"}) {
146+
edges {
147+
node {
148+
name
149+
relatedDummy {
150+
id
151+
name
152+
}
153+
}
154+
}
155+
}
156+
}
157+
"""
158+
Then the response status code should be 200
159+
And the header "Content-Type" should be equal to "application/json"
160+
And the JSON node "data.dummies.edges[0].node.name" should be equal to "Dummy #2"
161+
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
@@ -73,7 +73,7 @@ public function __invoke(string $resourceClass = null, string $rootClass = null,
7373
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
7474
$dataProviderContext = $resourceMetadata->getGraphqlAttribute('query', 'normalization_context', [], true);
7575
$dataProviderContext['attributes'] = $this->fieldsToAttributes($info);
76-
$dataProviderContext['filters'] = $args;
76+
$dataProviderContext['filters'] = $this->getNormalizedFilters($args);
7777

7878
if (isset($source[$rootProperty = $info->fieldName], $source[ItemNormalizer::ITEM_KEY])) {
7979
$rootResolvedFields = $this->identifiersExtractor->getIdentifiersFromItem(unserialize($source[ItemNormalizer::ITEM_KEY]));
@@ -160,4 +160,22 @@ private function getSubresource(string $rootClass, array $rootResolvedFields, ar
160160
'collection' => $isCollection,
161161
]);
162162
}
163+
164+
private function getNormalizedFilters(array $args): array
165+
{
166+
$filters = $args;
167+
foreach ($filters as $name => $value) {
168+
if (\is_array($value)) {
169+
$filters[$name] = $this->getNormalizedFilters($value);
170+
continue;
171+
}
172+
173+
if (strpos($name, '_')) {
174+
// Gives a chance to relations/nested fields.
175+
$filters[str_replace('_', '.', $name)] = $value;
176+
}
177+
}
178+
179+
return $filters;
180+
}
163181
}

src/GraphQl/Type/SchemaBuilder.php

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

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

0 commit comments

Comments
 (0)