Skip to content

Commit e41dcca

Browse files
ENGCOM-7729: 28584 fixed issue category query does not handle fragments #28710
- Merge Pull Request #28710 from magento/magento2:28584_graphql_fragment_categorylist - Merged commits: 1. 4511c46 2. f6e2e29 3. 6588700 4. a9d6eb3 5. 423939c 6. 0ac4bb1
2 parents 86ec99b + 0ac4bb1 commit e41dcca

File tree

6 files changed

+179
-32
lines changed

6 files changed

+179
-32
lines changed

app/code/Magento/CatalogGraphQl/Model/AttributesJoiner.php

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
namespace Magento\CatalogGraphQl\Model;
99

1010
use GraphQL\Language\AST\FieldNode;
11+
use GraphQL\Language\AST\InlineFragmentNode;
12+
use GraphQL\Language\AST\NodeKind;
1113
use Magento\Eav\Model\Entity\Collection\AbstractCollection;
14+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
1215

1316
/**
1417
* Joins attributes for provided field node field names.
@@ -43,11 +46,12 @@ public function __construct(array $fieldToAttributeMap = [])
4346
*
4447
* @param FieldNode $fieldNode
4548
* @param AbstractCollection $collection
49+
* @param ResolveInfo $resolveInfo
4650
* @return void
4751
*/
48-
public function join(FieldNode $fieldNode, AbstractCollection $collection): void
52+
public function join(FieldNode $fieldNode, AbstractCollection $collection, ResolveInfo $resolveInfo): void
4953
{
50-
foreach ($this->getQueryFields($fieldNode) as $field) {
54+
foreach ($this->getQueryFields($fieldNode, $resolveInfo) as $field) {
5155
$this->addFieldToCollection($collection, $field);
5256
}
5357
}
@@ -56,26 +60,70 @@ public function join(FieldNode $fieldNode, AbstractCollection $collection): void
5660
* Get an array of queried fields.
5761
*
5862
* @param FieldNode $fieldNode
63+
* @param ResolveInfo $resolveInfo
5964
* @return string[]
6065
*/
61-
public function getQueryFields(FieldNode $fieldNode): array
66+
public function getQueryFields(FieldNode $fieldNode, ResolveInfo $resolveInfo): array
6267
{
6368
if (null === $this->getFieldNodeSelections($fieldNode)) {
6469
$query = $fieldNode->selectionSet->selections;
6570
$selectedFields = [];
71+
$fragmentFields = [];
6672
/** @var FieldNode $field */
6773
foreach ($query as $field) {
68-
if ($field->kind === 'InlineFragment') {
69-
continue;
74+
if ($field->kind === NodeKind::INLINE_FRAGMENT) {
75+
$fragmentFields[] = $this->addInlineFragmentFields($resolveInfo, $field);
76+
} elseif ($field->kind === NodeKind::FRAGMENT_SPREAD &&
77+
($spreadFragmentNode = $resolveInfo->fragments[$field->name->value])) {
78+
79+
foreach ($spreadFragmentNode->selectionSet->selections as $spreadNode) {
80+
if (isset($spreadNode->selectionSet->selections)) {
81+
$fragmentFields[] = $this->getQueryFields($spreadNode, $resolveInfo);
82+
} else {
83+
$selectedFields[] = $spreadNode->name->value;
84+
}
85+
}
86+
} else {
87+
$selectedFields[] = $field->name->value;
7088
}
71-
$selectedFields[] = $field->name->value;
7289
}
73-
$this->setSelectionsForFieldNode($fieldNode, $selectedFields);
90+
if ($fragmentFields) {
91+
$selectedFields = array_merge($selectedFields, array_merge(...$fragmentFields));
92+
}
93+
$this->setSelectionsForFieldNode($fieldNode, array_unique($selectedFields));
7494
}
7595

7696
return $this->getFieldNodeSelections($fieldNode);
7797
}
7898

99+
/**
100+
* Add fields from inline fragment nodes
101+
*
102+
* @param ResolveInfo $resolveInfo
103+
* @param InlineFragmentNode $inlineFragmentField
104+
* @param array $inlineFragmentFields
105+
* @return string[]
106+
*/
107+
private function addInlineFragmentFields(
108+
ResolveInfo $resolveInfo,
109+
InlineFragmentNode $inlineFragmentField,
110+
$inlineFragmentFields = []
111+
): array {
112+
$query = $inlineFragmentField->selectionSet->selections;
113+
/** @var FieldNode $field */
114+
foreach ($query as $field) {
115+
if ($field->kind === NodeKind::INLINE_FRAGMENT) {
116+
$this->addInlineFragmentFields($resolveInfo, $field, $inlineFragmentFields);
117+
} elseif (isset($field->selectionSet->selections)) {
118+
continue;
119+
} else {
120+
$inlineFragmentFields[] = $field->name->value;
121+
}
122+
}
123+
124+
return array_unique($inlineFragmentFields);
125+
}
126+
79127
/**
80128
* Add field to collection select
81129
*

app/code/Magento/CatalogGraphQl/Model/Category/DepthCalculator.php

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
namespace Magento\CatalogGraphQl\Model\Category;
99

1010
use GraphQL\Language\AST\FieldNode;
11+
use GraphQL\Language\AST\InlineFragmentNode;
12+
use GraphQL\Language\AST\NodeKind;
13+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
1114

1215
/**
1316
* Used for determining the depth information for a requested category tree in a GraphQL request
@@ -17,22 +20,57 @@ class DepthCalculator
1720
/**
1821
* Calculate the total depth of a category tree inside a GraphQL request
1922
*
23+
* @param ResolveInfo $resolveInfo
2024
* @param FieldNode $fieldNode
2125
* @return int
2226
*/
23-
public function calculate(FieldNode $fieldNode) : int
27+
public function calculate(ResolveInfo $resolveInfo, FieldNode $fieldNode) : int
2428
{
2529
$selections = $fieldNode->selectionSet->selections ?? [];
2630
$depth = count($selections) ? 1 : 0;
2731
$childrenDepth = [0];
2832
foreach ($selections as $node) {
29-
if ($node->kind === 'InlineFragment' || null !== $node->alias) {
33+
if (isset($node->alias) && null !== $node->alias) {
3034
continue;
3135
}
3236

33-
$childrenDepth[] = $this->calculate($node);
37+
if ($node->kind === NodeKind::INLINE_FRAGMENT) {
38+
$childrenDepth[] = $this->addInlineFragmentDepth($resolveInfo, $node);
39+
} elseif ($node->kind === NodeKind::FRAGMENT_SPREAD && isset($resolveInfo->fragments[$node->name->value])) {
40+
foreach ($resolveInfo->fragments[$node->name->value]->selectionSet->selections as $spreadNode) {
41+
$childrenDepth[] = $this->calculate($resolveInfo, $spreadNode);
42+
}
43+
} else {
44+
$childrenDepth[] = $this->calculate($resolveInfo, $node);
45+
}
3446
}
3547

3648
return $depth + max($childrenDepth);
3749
}
50+
51+
/**
52+
* Add inline fragment fields into calculating of category depth
53+
*
54+
* @param ResolveInfo $resolveInfo
55+
* @param InlineFragmentNode $inlineFragmentField
56+
* @param array $depth
57+
* @return int
58+
*/
59+
private function addInlineFragmentDepth(
60+
ResolveInfo $resolveInfo,
61+
InlineFragmentNode $inlineFragmentField,
62+
$depth = []
63+
): int {
64+
$selections = $inlineFragmentField->selectionSet->selections;
65+
/** @var FieldNode $field */
66+
foreach ($selections as $field) {
67+
if ($field->kind === NodeKind::INLINE_FRAGMENT) {
68+
$depth[] = $this->addInlineFragmentDepth($resolveInfo, $field, $depth);
69+
} elseif ($field->selectionSet && $field->selectionSet->selections) {
70+
$depth[] = $this->calculate($resolveInfo, $field);
71+
}
72+
}
73+
74+
return $depth ? max($depth) : 0;
75+
}
3876
}

app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@
77

88
namespace Magento\CatalogGraphQl\Model\Resolver;
99

10-
use Magento\CatalogGraphQl\Model\Resolver\Product\ProductCategories;
11-
use Magento\Framework\Exception\LocalizedException;
12-
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
1310
use Magento\Catalog\Api\Data\CategoryInterface;
1411
use Magento\Catalog\Model\ResourceModel\Category\Collection;
1512
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory;
1613
use Magento\CatalogGraphQl\Model\AttributesJoiner;
14+
use Magento\CatalogGraphQl\Model\Category\Hydrator as CategoryHydrator;
15+
use Magento\CatalogGraphQl\Model\Resolver\Product\ProductCategories;
1716
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CustomAttributesFlattener;
17+
use Magento\Framework\Exception\LocalizedException;
1818
use Magento\Framework\GraphQl\Config\Element\Field;
19-
use Magento\Framework\GraphQl\Query\ResolverInterface;
2019
use Magento\Framework\GraphQl\Query\Resolver\ValueFactory;
21-
use Magento\CatalogGraphQl\Model\Category\Hydrator as CategoryHydrator;
20+
use Magento\Framework\GraphQl\Query\ResolverInterface;
21+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
2222
use Magento\Store\Model\StoreManagerInterface;
2323

2424
/**
@@ -121,7 +121,7 @@ function () use ($that, $categoryIds, $info) {
121121
}
122122

123123
if (!$this->collection->isLoaded()) {
124-
$that->attributesJoiner->join($info->fieldNodes[0], $this->collection);
124+
$that->attributesJoiner->join($info->fieldNodes[0], $this->collection, $info);
125125
$this->collection->addIdFilter($this->categoryIds);
126126
}
127127
/** @var CategoryInterface | \Magento\Catalog\Model\Category $item */
@@ -130,7 +130,7 @@ function () use ($that, $categoryIds, $info) {
130130
// Try to extract all requested fields from the loaded collection data
131131
$categories[$item->getId()] = $this->categoryHydrator->hydrateCategory($item, true);
132132
$categories[$item->getId()]['model'] = $item;
133-
$requestedFields = $that->attributesJoiner->getQueryFields($info->fieldNodes[0]);
133+
$requestedFields = $that->attributesJoiner->getQueryFields($info->fieldNodes[0], $info);
134134
$extractedFields = array_keys($categories[$item->getId()]);
135135
$foundFields = array_intersect($requestedFields, $extractedFields);
136136
if (count($requestedFields) === count($foundFields)) {

app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductFieldsSelector.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Magento\CatalogGraphQl\Model\Resolver\Product;
99

10+
use GraphQL\Language\AST\NodeKind;
1011
use Magento\Framework\GraphQl\Query\FieldTranslator;
1112
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
1213

@@ -43,9 +44,9 @@ public function getProductFieldsFromInfo(ResolveInfo $info, string $productNodeN
4344
continue;
4445
}
4546
foreach ($node->selectionSet->selections as $selectionNode) {
46-
if ($selectionNode->kind === 'InlineFragment') {
47+
if ($selectionNode->kind === NodeKind::INLINE_FRAGMENT) {
4748
foreach ($selectionNode->selectionSet->selections as $inlineSelection) {
48-
if ($inlineSelection->kind === 'InlineFragment') {
49+
if ($inlineSelection->kind === NodeKind::INLINE_FRAGMENT) {
4950
continue;
5051
}
5152
$fieldNames[] = $this->fieldTranslator->translate($inlineSelection->name->value);

app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@
88
namespace Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider;
99

1010
use GraphQL\Language\AST\FieldNode;
11-
use Magento\CatalogGraphQl\Model\Category\DepthCalculator;
12-
use Magento\CatalogGraphQl\Model\Category\LevelCalculator;
13-
use Magento\Framework\EntityManager\MetadataPool;
14-
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
11+
use GraphQL\Language\AST\NodeKind;
1512
use Magento\Catalog\Api\Data\CategoryInterface;
13+
use Magento\Catalog\Model\Category;
1614
use Magento\Catalog\Model\ResourceModel\Category\Collection;
1715
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory;
1816
use Magento\CatalogGraphQl\Model\AttributesJoiner;
19-
use Magento\Catalog\Model\Category;
17+
use Magento\CatalogGraphQl\Model\Category\DepthCalculator;
18+
use Magento\CatalogGraphQl\Model\Category\LevelCalculator;
19+
use Magento\Framework\EntityManager\MetadataPool;
20+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
2021

2122
/**
2223
* Category tree data provider
@@ -85,8 +86,8 @@ public function getTree(ResolveInfo $resolveInfo, int $rootCategoryId): \Iterato
8586
{
8687
$categoryQuery = $resolveInfo->fieldNodes[0];
8788
$collection = $this->collectionFactory->create();
88-
$this->joinAttributesRecursively($collection, $categoryQuery);
89-
$depth = $this->depthCalculator->calculate($categoryQuery);
89+
$this->joinAttributesRecursively($collection, $categoryQuery, $resolveInfo);
90+
$depth = $this->depthCalculator->calculate($resolveInfo, $categoryQuery);
9091
$level = $this->levelCalculator->calculate($rootCategoryId);
9192

9293
// If root category is being filter, we've to remove first slash
@@ -124,24 +125,27 @@ public function getTree(ResolveInfo $resolveInfo, int $rootCategoryId): \Iterato
124125
*
125126
* @param Collection $collection
126127
* @param FieldNode $fieldNode
128+
* @param ResolveInfo $resolveInfo
127129
* @return void
128130
*/
129-
private function joinAttributesRecursively(Collection $collection, FieldNode $fieldNode) : void
130-
{
131+
private function joinAttributesRecursively(
132+
Collection $collection,
133+
FieldNode $fieldNode,
134+
ResolveInfo $resolveInfo
135+
): void {
131136
if (!isset($fieldNode->selectionSet->selections)) {
132137
return;
133138
}
134139

135140
$subSelection = $fieldNode->selectionSet->selections;
136-
$this->attributesJoiner->join($fieldNode, $collection);
141+
$this->attributesJoiner->join($fieldNode, $collection, $resolveInfo);
137142

138143
/** @var FieldNode $node */
139144
foreach ($subSelection as $node) {
140-
if ($node->kind === 'InlineFragment') {
145+
if ($node->kind === NodeKind::INLINE_FRAGMENT || $node->kind === NodeKind::FRAGMENT_SPREAD) {
141146
continue;
142147
}
143-
144-
$this->joinAttributesRecursively($collection, $node);
148+
$this->joinAttributesRecursively($collection, $node, $resolveInfo);
145149
}
146150
}
147151
}

dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryListTest.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,4 +714,60 @@ private function assertCategoryChildren(array $category, array $expectedChildren
714714
$this->assertResponseFields($category['children'][$i], $expectedChild);
715715
}
716716
}
717+
718+
/**
719+
* @magentoApiDataFixture Magento/Catalog/_files/categories.php
720+
*/
721+
public function testFilterCategoryInlineFragment()
722+
{
723+
$query = <<<QUERY
724+
{
725+
categoryList(filters: {ids: {eq: "6"}}){
726+
... on CategoryTree {
727+
id
728+
name
729+
url_key
730+
url_path
731+
children_count
732+
path
733+
position
734+
}
735+
}
736+
}
737+
QUERY;
738+
$result = $this->graphQlQuery($query);
739+
$this->assertArrayNotHasKey('errors', $result);
740+
$this->assertCount(1, $result['categoryList']);
741+
$this->assertEquals($result['categoryList'][0]['name'], 'Category 2');
742+
$this->assertEquals($result['categoryList'][0]['url_path'], 'category-2');
743+
}
744+
745+
/**
746+
* @magentoApiDataFixture Magento/Catalog/_files/categories.php
747+
*/
748+
public function testFilterCategoryNamedFragment()
749+
{
750+
$query = <<<QUERY
751+
{
752+
categoryList(filters: {ids: {eq: "6"}}){
753+
...Cat
754+
}
755+
}
756+
757+
fragment Cat on CategoryTree {
758+
id
759+
name
760+
url_key
761+
url_path
762+
children_count
763+
path
764+
position
765+
}
766+
QUERY;
767+
$result = $this->graphQlQuery($query);
768+
$this->assertArrayNotHasKey('errors', $result);
769+
$this->assertCount(1, $result['categoryList']);
770+
$this->assertEquals($result['categoryList'][0]['name'], 'Category 2');
771+
$this->assertEquals($result['categoryList'][0]['url_path'], 'category-2');
772+
}
717773
}

0 commit comments

Comments
 (0)