Skip to content

Commit 794977a

Browse files
authored
Merge pull request #4531 from magento-honey-badgers/MC-17015
[honey] MC-17015: [GraphQL] Product Categories query doesn't return all categories where product is visible
2 parents 80ae9bd + 326e0b3 commit 794977a

File tree

6 files changed

+405
-43
lines changed

6 files changed

+405
-43
lines changed

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

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

88
namespace Magento\CatalogGraphQl\Model\Resolver;
99

10+
use Magento\CatalogGraphQl\Model\Resolver\Product\ProductCategories;
1011
use Magento\Framework\Exception\LocalizedException;
1112
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
1213
use Magento\Catalog\Api\Data\CategoryInterface;
@@ -18,6 +19,7 @@
1819
use Magento\Framework\GraphQl\Query\ResolverInterface;
1920
use Magento\Framework\GraphQl\Query\Resolver\ValueFactory;
2021
use Magento\CatalogGraphQl\Model\Category\Hydrator as CategoryHydrator;
22+
use Magento\Store\Model\StoreManagerInterface;
2123

2224
/**
2325
* Resolver for category objects the product is assigned to.
@@ -56,25 +58,41 @@ class Categories implements ResolverInterface
5658
*/
5759
private $categoryHydrator;
5860

61+
/**
62+
* @var ProductCategories
63+
*/
64+
private $productCategories;
65+
66+
/**
67+
* @var StoreManagerInterface
68+
*/
69+
private $storeManager;
70+
5971
/**
6072
* @param CollectionFactory $collectionFactory
6173
* @param AttributesJoiner $attributesJoiner
6274
* @param CustomAttributesFlattener $customAttributesFlattener
6375
* @param ValueFactory $valueFactory
6476
* @param CategoryHydrator $categoryHydrator
77+
* @param ProductCategories $productCategories
78+
* @param StoreManagerInterface $storeManager
6579
*/
6680
public function __construct(
6781
CollectionFactory $collectionFactory,
6882
AttributesJoiner $attributesJoiner,
6983
CustomAttributesFlattener $customAttributesFlattener,
7084
ValueFactory $valueFactory,
71-
CategoryHydrator $categoryHydrator
85+
CategoryHydrator $categoryHydrator,
86+
ProductCategories $productCategories,
87+
StoreManagerInterface $storeManager
7288
) {
7389
$this->collection = $collectionFactory->create();
7490
$this->attributesJoiner = $attributesJoiner;
7591
$this->customAttributesFlattener = $customAttributesFlattener;
7692
$this->valueFactory = $valueFactory;
7793
$this->categoryHydrator = $categoryHydrator;
94+
$this->productCategories = $productCategories;
95+
$this->storeManager = $storeManager;
7896
}
7997

8098
/**
@@ -90,39 +108,42 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
90108

91109
/** @var \Magento\Catalog\Model\Product $product */
92110
$product = $value['model'];
93-
$categoryIds = $product->getCategoryIds();
111+
$storeId = $this->storeManager->getStore()->getId();
112+
$categoryIds = $this->productCategories->getCategoryIdsByProduct((int)$product->getId(), (int)$storeId);
94113
$this->categoryIds = array_merge($this->categoryIds, $categoryIds);
95114
$that = $this;
96115

97-
return $this->valueFactory->create(function () use ($that, $categoryIds, $info) {
98-
$categories = [];
99-
if (empty($that->categoryIds)) {
100-
return [];
101-
}
116+
return $this->valueFactory->create(
117+
function () use ($that, $categoryIds, $info) {
118+
$categories = [];
119+
if (empty($that->categoryIds)) {
120+
return [];
121+
}
102122

103-
if (!$this->collection->isLoaded()) {
104-
$that->attributesJoiner->join($info->fieldNodes[0], $this->collection);
105-
$this->collection->addIdFilter($this->categoryIds);
106-
}
107-
/** @var CategoryInterface | \Magento\Catalog\Model\Category $item */
108-
foreach ($this->collection as $item) {
109-
if (in_array($item->getId(), $categoryIds)) {
110-
// Try to extract all requested fields from the loaded collection data
111-
$categories[$item->getId()] = $this->categoryHydrator->hydrateCategory($item, true);
112-
$categories[$item->getId()]['model'] = $item;
113-
$requestedFields = $that->attributesJoiner->getQueryFields($info->fieldNodes[0]);
114-
$extractedFields = array_keys($categories[$item->getId()]);
115-
$foundFields = array_intersect($requestedFields, $extractedFields);
116-
if (count($requestedFields) === count($foundFields)) {
117-
continue;
118-
}
123+
if (!$this->collection->isLoaded()) {
124+
$that->attributesJoiner->join($info->fieldNodes[0], $this->collection);
125+
$this->collection->addIdFilter($this->categoryIds);
126+
}
127+
/** @var CategoryInterface | \Magento\Catalog\Model\Category $item */
128+
foreach ($this->collection as $item) {
129+
if (in_array($item->getId(), $categoryIds)) {
130+
// Try to extract all requested fields from the loaded collection data
131+
$categories[$item->getId()] = $this->categoryHydrator->hydrateCategory($item, true);
132+
$categories[$item->getId()]['model'] = $item;
133+
$requestedFields = $that->attributesJoiner->getQueryFields($info->fieldNodes[0]);
134+
$extractedFields = array_keys($categories[$item->getId()]);
135+
$foundFields = array_intersect($requestedFields, $extractedFields);
136+
if (count($requestedFields) === count($foundFields)) {
137+
continue;
138+
}
119139

120-
// If not all requested fields were extracted from the collection, start more complex extraction
121-
$categories[$item->getId()] = $this->categoryHydrator->hydrateCategory($item);
140+
// If not all requested fields were extracted from the collection, start more complex extraction
141+
$categories[$item->getId()] = $this->categoryHydrator->hydrateCategory($item);
142+
}
122143
}
123-
}
124144

125-
return $categories;
126-
});
145+
return $categories;
146+
}
147+
);
127148
}
128149
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CatalogGraphQl\Model\Resolver\Product;
9+
10+
use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction;
11+
use Magento\Framework\App\ResourceConnection;
12+
use Magento\Framework\Indexer\DimensionFactory;
13+
use Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver;
14+
use Magento\Store\Model\Group;
15+
use Magento\Store\Model\Store;
16+
17+
/**
18+
* Get all categories where product is visible
19+
*/
20+
class ProductCategories
21+
{
22+
/**
23+
* @var IndexScopeResolver
24+
*/
25+
private $tableResolver;
26+
27+
/**
28+
* @var ResourceConnection
29+
*/
30+
private $resourceConnection;
31+
32+
/**
33+
* @var DimensionFactory
34+
*/
35+
private $dimensionFactory;
36+
37+
/**
38+
* @param IndexScopeResolver $tableResolver
39+
* @param ResourceConnection $resourceConnection
40+
* @param DimensionFactory $dimensionFactory
41+
*/
42+
public function __construct(
43+
IndexScopeResolver $tableResolver,
44+
ResourceConnection $resourceConnection,
45+
DimensionFactory $dimensionFactory
46+
) {
47+
$this->tableResolver = $tableResolver;
48+
$this->resourceConnection = $resourceConnection;
49+
$this->dimensionFactory = $dimensionFactory;
50+
}
51+
52+
/**
53+
* Get category ids for product
54+
*
55+
* @param int $productId
56+
* @param int $storeId
57+
* @return array
58+
*/
59+
public function getCategoryIdsByProduct(int $productId, int $storeId)
60+
{
61+
$connection = $this->resourceConnection->getConnection();
62+
$categoryProductTable = $this->getCatalogCategoryProductTableName($storeId);
63+
$storeTable = $this->resourceConnection->getTableName(Store::ENTITY);
64+
$storeGroupTable = $this->resourceConnection->getTableName(Group::ENTITY);
65+
66+
$select = $connection->select()
67+
->from(['cat_index' => $categoryProductTable], ['category_id'])
68+
->joinInner(['store' => $storeTable], $connection->quoteInto('store.store_id = ?', $storeId), [])
69+
->joinInner(
70+
['store_group' => $storeGroupTable],
71+
'store.group_id = store_group.group_id AND cat_index.category_id != store_group.root_category_id',
72+
[]
73+
)
74+
->where('product_id = ?', $productId);
75+
76+
$categoryIds = $connection->fetchCol($select);
77+
78+
return $categoryIds;
79+
}
80+
81+
/**
82+
* Get catalog_category_product table name
83+
*
84+
* @param int $storeId
85+
* @return string
86+
*/
87+
private function getCatalogCategoryProductTableName(int $storeId)
88+
{
89+
$dimension = $this->dimensionFactory->create(Store::ENTITY, (string)$storeId);
90+
$tableName = $this->tableResolver->resolve(
91+
AbstractAction::MAIN_INDEX_TABLE,
92+
[$dimension]
93+
);
94+
95+
return $tableName;
96+
}
97+
}

0 commit comments

Comments
 (0)