Skip to content

Commit 4d193eb

Browse files
committed
ACP2E-3190: [Cloud] Products graphql having error when same simple product has assigned to multiple configurable products
- Initial Commit
1 parent 11e7d89 commit 4d193eb

File tree

3 files changed

+250
-14
lines changed

3 files changed

+250
-14
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
/************************************************************************
3+
*
4+
* Copyright 2024 Adobe
5+
* All Rights Reserved.
6+
*
7+
* NOTICE: All information contained herein is, and remains
8+
* the property of Adobe and its suppliers, if any. The intellectual
9+
* and technical concepts contained herein are proprietary to Adobe
10+
* and its suppliers and are protected by all applicable intellectual
11+
* property laws, including trade secret and copyright laws.
12+
* Dissemination of this information or reproduction of this material
13+
* is strictly forbidden unless prior written permission is obtained
14+
* from Adobe.
15+
* ************************************************************************
16+
*/
17+
declare(strict_types=1);
18+
19+
namespace Magento\ConfigurableProductGraphQl\Model\ResourceModel\Product\Type;
20+
21+
use Exception;
22+
use Magento\Framework\Exception\LocalizedException;
23+
use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable as ConfigurableResourceModel;
24+
25+
/**
26+
* Get configurable product children ids by parent ids
27+
*/
28+
class GetChildrenIdsByParentId
29+
{
30+
/**
31+
* Initialize
32+
*
33+
* @param ConfigurableResourceModel $resourceModel
34+
*/
35+
public function __construct(
36+
private readonly ConfigurableResourceModel $resourceModel
37+
) {
38+
}
39+
40+
/**
41+
* Retrieve Required children ids by parent ids
42+
*
43+
* @param array $parentIds
44+
* @return array
45+
* @throws LocalizedException|Exception
46+
*/
47+
public function execute(array $parentIds): array
48+
{
49+
$select = $this->resourceModel->getConnection()->select()->from(
50+
['l' => $this->resourceModel->getMainTable()],
51+
['product_id', 'parent_id']
52+
)->where(
53+
'parent_id IN (?)',
54+
$parentIds,
55+
\Zend_Db::INT_TYPE
56+
);
57+
58+
$childrenIds = [];
59+
foreach ($this->resourceModel->getConnection()->fetchAll($select) as $row) {
60+
$childrenIds[$row['product_id']][] = $row['parent_id'];
61+
}
62+
63+
return $childrenIds;
64+
}
65+
}

app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99

1010
use Magento\Catalog\Api\Data\ProductInterface;
1111
use Magento\Catalog\Model\Product;
12-
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
13-
use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection as ChildCollection;
14-
use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\CollectionFactory;
12+
use Magento\ConfigurableProductGraphQl\Model\ResourceModel\Product\Type\GetChildrenIdsByParentId;
13+
use Magento\Catalog\Model\ResourceModel\Product\Collection as ChildCollection;
14+
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
15+
use Magento\Framework\App\ObjectManager;
1516
use Magento\Framework\EntityManager\MetadataPool;
1617
use Magento\Framework\Api\SearchCriteriaBuilder;
18+
use Magento\Framework\Exception\LocalizedException;
1719
use Magento\Framework\ObjectManager\ResetAfterRequestInterface;
1820
use Magento\GraphQl\Model\Query\ContextInterface;
1921
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionProcessorInterface;
@@ -22,6 +24,7 @@
2224

2325
/**
2426
* Collection for fetching configurable child product data.
27+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
2528
*/
2629
class Collection implements ResetAfterRequestInterface
2730
{
@@ -65,32 +68,42 @@ class Collection implements ResetAfterRequestInterface
6568
*/
6669
private $collectionPostProcessor;
6770

71+
/**
72+
* @var GetChildrenIdsByParentId
73+
*/
74+
private $getChildrenIdsByParentId;
75+
6876
/**
6977
* @param CollectionFactory $childCollectionFactory
7078
* @param SearchCriteriaBuilder $searchCriteriaBuilder
7179
* @param MetadataPool $metadataPool
7280
* @param CollectionProcessorInterface $collectionProcessor
7381
* @param CollectionPostProcessor $collectionPostProcessor
82+
* @param GetChildrenIdsByParentId|null $getChildrenIdsByParentId
7483
*/
7584
public function __construct(
7685
CollectionFactory $childCollectionFactory,
7786
SearchCriteriaBuilder $searchCriteriaBuilder,
7887
MetadataPool $metadataPool,
7988
CollectionProcessorInterface $collectionProcessor,
80-
CollectionPostProcessor $collectionPostProcessor
89+
CollectionPostProcessor $collectionPostProcessor,
90+
?GetChildrenIdsByParentId $getChildrenIdsByParentId = null
8191
) {
8292
$this->childCollectionFactory = $childCollectionFactory;
8393
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
8494
$this->metadataPool = $metadataPool;
8595
$this->collectionProcessor = $collectionProcessor;
8696
$this->collectionPostProcessor = $collectionPostProcessor;
97+
$this->getChildrenIdsByParentId = $getChildrenIdsByParentId
98+
?: ObjectManager::getInstance()->get(GetChildrenIdsByParentId::class);
8799
}
88100

89101
/**
90102
* Add parent to collection filter
91103
*
92104
* @param Product $product
93105
* @return void
106+
* @throws \Exception
94107
*/
95108
public function addParentProduct(Product $product) : void
96109
{
@@ -125,6 +138,7 @@ public function addEavAttributes(array $attributeCodes) : void
125138
* @param ContextInterface $context
126139
* @param array $attributeCodes
127140
* @return array
141+
* @throws LocalizedException
128142
*/
129143
public function getChildProductsByParentId(int $id, ContextInterface $context, array $attributeCodes) : array
130144
{
@@ -143,6 +157,7 @@ public function getChildProductsByParentId(int $id, ContextInterface $context, a
143157
* @param ContextInterface $context
144158
* @param array $attributeCodes
145159
* @return array
160+
* @throws LocalizedException
146161
*/
147162
private function fetch(ContextInterface $context, array $attributeCodes) : array
148163
{
@@ -152,11 +167,9 @@ private function fetch(ContextInterface $context, array $attributeCodes) : array
152167

153168
/** @var ChildCollection $childCollection */
154169
$childCollection = $this->childCollectionFactory->create();
155-
foreach ($this->parentProducts as $product) {
156-
$childCollection->setProductFilter($product);
157-
}
170+
$childrenIdsByParent = $this->getChildrenIdsByParentId->execute(array_keys($this->parentProducts));
158171
$childCollection->addWebsiteFilter($context->getExtensionAttributes()->getStore()->getWebsiteId());
159-
172+
$childCollection->addIdFilter(array_keys($childrenIdsByParent));
160173
$attributeCodes = array_unique(array_merge($this->attributeCodes, $attributeCodes));
161174

162175
$this->collectionProcessor->process(
@@ -173,14 +186,13 @@ private function fetch(ContextInterface $context, array $attributeCodes) : array
173186
continue;
174187
}
175188
$formattedChild = ['model' => $childProduct, 'sku' => $childProduct->getSku()];
176-
$parentId = (int)$childProduct->getParentId();
177-
if (!isset($this->childrenMap[$parentId])) {
178-
$this->childrenMap[$parentId] = [];
189+
foreach ($childrenIdsByParent[$childProduct->getId()] as $parentId) {
190+
if (!isset($this->childrenMap[$parentId])) {
191+
$this->childrenMap[$parentId] = [];
192+
}
193+
$this->childrenMap[$parentId][] = $formattedChild;
179194
}
180-
181-
$this->childrenMap[$parentId][] = $formattedChild;
182195
}
183-
184196
return $this->childrenMap;
185197
}
186198

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<?php
2+
/************************************************************************
3+
*
4+
* Copyright 2024 Adobe
5+
* All Rights Reserved.
6+
*
7+
* NOTICE: All information contained herein is, and remains
8+
* the property of Adobe and its suppliers, if any. The intellectual
9+
* and technical concepts contained herein are proprietary to Adobe
10+
* and its suppliers and are protected by all applicable intellectual
11+
* property laws, including trade secret and copyright laws.
12+
* Dissemination of this information or reproduction of this material
13+
* is strictly forbidden unless prior written permission is obtained
14+
* from Adobe.
15+
* ************************************************************************
16+
*/
17+
declare(strict_types=1);
18+
19+
namespace Magento\GraphQl\ConfigurableProduct;
20+
21+
use Magento\Catalog\Test\Fixture\Category as CategoryFixture;
22+
use Magento\Catalog\Test\Fixture\Product as ProductFixture;
23+
use Magento\ConfigurableProduct\Test\Fixture\Attribute;
24+
use Magento\ConfigurableProduct\Test\Fixture\Product as ConfigurableProductFixture;
25+
use Magento\TestFramework\Fixture\DataFixture;
26+
use Magento\TestFramework\TestCase\GraphQlAbstract;
27+
28+
/**
29+
* Test configurable product queries work correctly
30+
*/
31+
class MultipleConfigurableProductWithSameSimpleProductTest extends GraphQlAbstract
32+
{
33+
/**
34+
* Test if multiple configurable product can have same simple product
35+
*
36+
*/
37+
#[
38+
DataFixture(CategoryFixture::class, ['name' => 'Test category'], 'test_category'),
39+
DataFixture(Attribute::class, ['options' => [['label' => 'color', 'sort_order' => 0]]], as:'attribute'),
40+
DataFixture(
41+
ProductFixture::class,
42+
[
43+
'name' => 'Simple Product in Test Category',
44+
'sku' => 'simple-product-1',
45+
'category_ids' => ['$test_category.id$'],
46+
'price' => 10,
47+
],
48+
'simple_product_1'
49+
),
50+
DataFixture(
51+
ConfigurableProductFixture::class,
52+
[
53+
'name' => 'Configurable Product 1',
54+
'sku' => 'configurable_product_1',
55+
'category_ids' => ['$test_category.id$'],
56+
'_options' => ['$attribute$'],
57+
'_links' => [
58+
'$simple_product_1$'
59+
]
60+
],
61+
'configurable-product-1'
62+
),
63+
DataFixture(
64+
ConfigurableProductFixture::class,
65+
[
66+
'name' => 'Configurable Product 2',
67+
'sku' => 'configurable_product_2',
68+
'category_ids' => ['$test_category.id$'],
69+
'_options' => ['$attribute$'],
70+
'_links' => [
71+
'$simple_product_1$'
72+
]
73+
],
74+
'configurable-product-2'
75+
),
76+
]
77+
public function testMultipleConfigurableProductCanHaveSameSimpleProduct()
78+
{
79+
$query = $this->getGraphQlQuery('"configurable_product_1", "configurable_product_2"');
80+
$result = $this->graphQlQuery($query);
81+
82+
self::assertArrayNotHasKey('errors', $result);
83+
self::assertNotEmpty($result['products']);
84+
}
85+
86+
/**
87+
* Get GraphQl products query with aggregations
88+
*
89+
* @param string $skus
90+
* @return string
91+
*/
92+
private function getGraphQlQuery(string $skus)
93+
{
94+
return <<<QUERY
95+
{
96+
products(filter: {sku: {in: [{$skus}]}}) {
97+
items {
98+
id
99+
attribute_set_id
100+
name
101+
sku
102+
__typename
103+
price {
104+
regularPrice {
105+
amount {
106+
currency
107+
value
108+
}
109+
}
110+
}
111+
categories {
112+
id
113+
}
114+
... on ConfigurableProduct {
115+
configurable_options {
116+
id
117+
attribute_id_v2
118+
label
119+
position
120+
use_default
121+
attribute_code
122+
values {
123+
value_index
124+
label
125+
}
126+
product_id
127+
}
128+
variants {
129+
product {
130+
id
131+
name
132+
sku
133+
attribute_set_id
134+
... on PhysicalProductInterface {
135+
weight
136+
}
137+
price_range{
138+
minimum_price{
139+
regular_price{
140+
value
141+
currency
142+
}
143+
}
144+
}
145+
}
146+
attributes {
147+
uid
148+
label
149+
code
150+
value_index
151+
}
152+
}
153+
}
154+
}
155+
}
156+
}
157+
QUERY;
158+
}
159+
}

0 commit comments

Comments
 (0)