Skip to content

Commit 9f22a65

Browse files
akaplyaLucianRadu
andauthored
Missing options fix (#239)
* MDEE-415: Add whole additional data json for swatch (#242) --------- Co-authored-by: LucianRadu <lucian.radu91@gmail.com>
1 parent 77c2c33 commit 9f22a65

File tree

5 files changed

+127
-108
lines changed

5 files changed

+127
-108
lines changed

CatalogDataExporter/etc/et_schema.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@
197197
<field name="required" type="Boolean" />
198198
<field name="renderType" type="String" />
199199
<field name="type" type="String" />
200+
<field name="swatchType" type="String" />
200201
<field name="values" type="ProductOptionValue" repeated="true" />
201202
</record>
202203

@@ -205,7 +206,9 @@
205206
<field name="label" type="String" />
206207
<field name="sortOrder" type="Int" />
207208
<field name="isDefault" type="Boolean" />
209+
<field name="colorHex" type="String" />
208210
<field name="imageUrl" type="String" />
211+
<field name="customSwatchValue" type="String" />
209212
<field name="qtyMutability" type="Boolean" />
210213
<field name="qty" type="Float" />
211214
<field name="infoUrl" type="String" />

ConfigurableProductDataExporter/Model/Provider/Product/Options.php

Lines changed: 103 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,22 @@
88
namespace Magento\ConfigurableProductDataExporter\Model\Provider\Product;
99

1010
use Exception;
11-
use Generator;
1211
use Magento\CatalogDataExporter\Model\Provider\Product\OptionProviderInterface;
1312
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
1413
use Magento\ConfigurableProductDataExporter\Model\Query\ProductOptionQuery;
1514
use Magento\ConfigurableProductDataExporter\Model\Query\ProductOptionValueQuery;
1615
use Magento\DataExporter\Exception\UnableRetrieveData;
17-
use Magento\Framework\App\ResourceConnection;
18-
use Magento\Framework\DB\Query\BatchIteratorInterface;
19-
use Magento\Framework\DB\Query\Generator as QueryGenerator;
20-
use Magento\Framework\DB\Select;
2116
use Magento\DataExporter\Model\Logging\CommerceDataExportLoggerInterface as LoggerInterface;
22-
use Throwable;
17+
use Magento\Framework\App\ResourceConnection;
18+
use Magento\Framework\DB\Sql\ColumnValueExpression;
19+
use Magento\Swatches\Helper\Media as MediaHelper;
20+
use Magento\Swatches\Model\Swatch;
2321

2422
/**
2523
* Configurable product options data provider
2624
*/
2725
class Options implements OptionProviderInterface
2826
{
29-
/**
30-
* Batch sizing for performing queries
31-
*
32-
* @var int
33-
*/
34-
private $batchSize;
3527

3628
/**
3729
* @var ResourceConnection
@@ -53,70 +45,93 @@ class Options implements OptionProviderInterface
5345
*/
5446
private $logger;
5547

56-
/**
57-
* @var QueryGenerator
58-
*/
59-
private $queryGenerator;
60-
6148
/**
6249
* @var ConfigurableOptionValueUid
6350
*/
6451
private $optionValueUid;
6552

66-
private static $optionValuesPerAttributesCache = [];
53+
/**
54+
* @var MediaHelper
55+
*/
56+
private $mediaHelper;
6757

6858
/**
6959
* @param ResourceConnection $resourceConnection
7060
* @param ProductOptionQuery $productOptionQuery
7161
* @param ProductOptionValueQuery $productOptionValueQuery
72-
* @param QueryGenerator $queryGenerator
7362
* @param ConfigurableOptionValueUid $optionValueUid
63+
* @param MediaHelper $mediaHelper
7464
* @param LoggerInterface $logger
75-
* @param int $batchSize
7665
*/
7766
public function __construct(
7867
ResourceConnection $resourceConnection,
7968
ProductOptionQuery $productOptionQuery,
8069
ProductOptionValueQuery $productOptionValueQuery,
81-
QueryGenerator $queryGenerator,
8270
ConfigurableOptionValueUid $optionValueUid,
83-
LoggerInterface $logger,
84-
int $batchSize
71+
MediaHelper $mediaHelper,
72+
LoggerInterface $logger
8573
) {
8674
$this->resourceConnection = $resourceConnection;
8775
$this->productOptionQuery = $productOptionQuery;
8876
$this->productOptionValueQuery = $productOptionValueQuery;
89-
$this->queryGenerator = $queryGenerator;
9077
$this->optionValueUid = $optionValueUid;
78+
$this->mediaHelper = $mediaHelper;
9179
$this->logger = $logger;
92-
$this->batchSize = $batchSize;
9380
}
9481

9582
/**
96-
* Retrieve query data in batches
83+
* Returns table name
9784
*
98-
* @param Select $select
99-
* @param string $rangeField
100-
* @return Generator
101-
* @throws UnableRetrieveData
85+
* @param string $reference
86+
* @return string
10287
*/
103-
private function getBatchedQueryData(Select $select, string $rangeField): Generator
88+
private function getTable(string $reference)
10489
{
105-
try {
106-
$connection = $this->resourceConnection->getConnection();
107-
$iterator = $this->queryGenerator->generate(
108-
$rangeField,
109-
$select,
110-
$this->batchSize,
111-
BatchIteratorInterface::NON_UNIQUE_FIELD_ITERATOR
90+
return $this->resourceConnection->getTableName($reference);
91+
}
92+
93+
/**
94+
* Returns possible attribute valies for a product
95+
*
96+
* @param int $entityId
97+
* @param int $attributeId
98+
* @return array
99+
*/
100+
private function getPossibleAttributeValues($entityId, $attributeId)
101+
{
102+
$connection = $this->resourceConnection->getConnection();
103+
$joinField = $connection->getAutoIncrementField($this->getTable('catalog_product_entity'));
104+
$select = $connection->select()
105+
->from(['cpe' => $this->getTable('catalog_product_entity')], [])
106+
->join(
107+
['psl' => $this->getTable('catalog_product_super_link')],
108+
sprintf('psl.parent_id = cpe.%s', $joinField),
109+
[]
110+
)
111+
->join(
112+
['iss' => $this->getTable('cataloginventory_stock_status')],
113+
'iss.product_id = psl.product_id AND iss.stock_status = 1',
114+
[]
115+
)
116+
->join(
117+
['cpc' => $this->getTable('catalog_product_entity')],
118+
'cpc.entity_id = psl.product_id',
119+
[]
120+
)
121+
->join(
122+
['cpi' => $this->getTable('catalog_product_entity_int')],
123+
sprintf(
124+
'cpi.%1$s = cpc.%1$s AND cpi.store_id = 0 AND cpi.attribute_id = %2$d',
125+
$joinField,
126+
$attributeId
127+
),
128+
[]
129+
)
130+
->where('cpe.entity_id = ?', $entityId)
131+
->columns(
132+
new ColumnValueExpression('DISTINCT cpi.value')
112133
);
113-
foreach ($iterator as $batchSelect) {
114-
yield $connection->fetchAll($batchSelect);
115-
}
116-
} catch (Throwable $exception) {
117-
$this->logger->error($exception->getMessage(), ['exception' => $exception]);
118-
throw new UnableRetrieveData('Unable to retrieve configurable option data');
119-
}
134+
return $connection->fetchCol($select);
120135
}
121136

122137
/**
@@ -128,37 +143,32 @@ private function getBatchedQueryData(Select $select, string $rangeField): Genera
128143
*/
129144
private function getOptionValuesData(array $arguments): array
130145
{
131-
$attributeIdsOrigin = $this->getAttributeIds($arguments);
132-
$attributeIds = \array_diff(
133-
$attributeIdsOrigin,
134-
\array_keys(self::$optionValuesPerAttributesCache)
135-
);
146+
$arguments['attributes'] = $this->getAttributeIds($arguments);
147+
$select = $this->productOptionValueQuery->getQuery($arguments);
136148

137-
if (!$attributeIds) {
138-
// get from cache
139-
return $this->getOptionValuesFromCache($attributeIdsOrigin);
140-
}
141-
$arguments['attributes'] = $attributeIds;
149+
$cursor = $this->resourceConnection->getConnection()->query($select);
142150

143-
$select = $this->productOptionValueQuery->getQuery($arguments);
144-
// ad hoc solution to check application cache size. should be replaced with generic approach
145-
$cacheSize = \strlen(\json_encode(self::$optionValuesPerAttributesCache));
146-
if ($cacheSize > 1024 * 1024 * 20) {
147-
self::$optionValuesPerAttributesCache = [];
148-
}
149-
foreach ($this->getBatchedQueryData($select, 'attribute_id') as $batchData) {
150-
foreach ($batchData as $row) {
151-
self::$optionValuesPerAttributesCache[$row['attribute_id']][$row['storeViewCode']][$row['optionId']] = [
152-
'id' => $this->optionValueUid->resolve($row['attribute_id'], $row['optionId']),
153-
'label' => $row['label'],
154-
];
155-
}
151+
$data = [];
152+
while ($row = $cursor->fetch()) {
153+
$data[$row['attribute_id']][$row['storeViewCode']][$row['optionId']] = [
154+
'id' => $this->optionValueUid->resolve($row['attribute_id'], $row['optionId']),
155+
'label' => $row['label'],
156+
'colorHex' => $row['swatchType'] == Swatch::SWATCH_TYPE_VISUAL_COLOR
157+
? $row['swatchValue'] : null,
158+
'imageUrl' => $row['swatchType'] == Swatch::SWATCH_TYPE_VISUAL_IMAGE
159+
? $this->mediaHelper->getSwatchMediaUrl() . $row['swatchValue'] : null,
160+
'customSwatchValue' => !in_array(
161+
$row['swatchType'],
162+
[Swatch::SWATCH_TYPE_TEXTUAL, Swatch::SWATCH_TYPE_VISUAL_COLOR, Swatch::SWATCH_TYPE_VISUAL_IMAGE]
163+
) ? $row['swatchValue'] : null
164+
];
156165
}
157-
158-
return $this->getOptionValuesFromCache($attributeIdsOrigin);
166+
return $data;
159167
}
160168

161169
/**
170+
* Returns attribute IDs associated with this product
171+
*
162172
* @param array $arguments
163173
* @return array
164174
*/
@@ -200,7 +210,8 @@ private function formatOptionsRow($row): array
200210
'id' => $row['attribute_code'],
201211
'type' => ConfigurableOptionValueUid::OPTION_TYPE,
202212
'label' => $row['label'],
203-
'sortOrder' => $row['position']
213+
'sortOrder' => $row['position'],
214+
'swatchType' => $row['swatchType']
204215
],
205216
];
206217
}
@@ -221,11 +232,12 @@ private function getOptionKey($row): string
221232
*/
222233
public function get(array $values): array
223234
{
235+
$temp = [];
224236

225237
$queryArguments = [];
226238
foreach ($values as $value) {
227239
if (!isset($value['productId'], $value['type'], $value['storeViewCode'])
228-
|| $value['type'] !== Configurable::TYPE_CODE ) {
240+
|| $value['type'] !== Configurable::TYPE_CODE) {
229241
continue;
230242
}
231243
$queryArguments['productId'][$value['productId']] = $value['productId'];
@@ -240,17 +252,24 @@ public function get(array $values): array
240252
$optionValuesData = $this->getOptionValuesData($queryArguments);
241253

242254
$select = $this->productOptionQuery->getQuery($queryArguments);
243-
foreach ($this->getBatchedQueryData($select, 'entity_id') as $batchData) {
244-
foreach ($batchData as $row) {
245-
$key = $this->getOptionKey($row);
246-
$options[$key] = $options[$key] ?? $this->formatOptionsRow($row);
247255

248-
if (isset($optionValuesData[$row['attribute_id']][$row['storeViewCode']])) {
249-
$options[$key]['optionsV2']['values'] = $this->getAssignedAttributeValues(
250-
$optionValuesData[$row['attribute_id']][$row['storeViewCode']],
251-
explode(',', $row['attributeValues'])
252-
);
253-
}
256+
$cursor = $this->resourceConnection->getConnection()->query($select);
257+
258+
while ($row = $cursor->fetch()) {
259+
if (!isset($temp[$row['productId'] . '-' . $row['attribute_id']])) {
260+
$temp[$row['productId'] . '-' . $row['attribute_id']] =
261+
$this->getPossibleAttributeValues($row['productId'], $row['attribute_id']);
262+
}
263+
$filter = $temp[$row['productId'] . '-' . $row['attribute_id']];
264+
265+
$key = $this->getOptionKey($row);
266+
$options[$key] = $options[$key] ?? $this->formatOptionsRow($row);
267+
268+
if (isset($optionValuesData[$row['attribute_id']][$row['storeViewCode']])) {
269+
$options[$key]['optionsV2']['values'] = $this->getAssignedAttributeValues(
270+
$optionValuesData[$row['attribute_id']][$row['storeViewCode']],
271+
$filter
272+
);
254273
}
255274
}
256275
} catch (Exception $exception) {
@@ -261,15 +280,8 @@ public function get(array $values): array
261280
}
262281

263282
/**
264-
* @param array|null $attributeIds
265-
* @return array
266-
*/
267-
private function getOptionValuesFromCache(?array $attributeIds): array
268-
{
269-
return \array_intersect_key(self::$optionValuesPerAttributesCache, \array_flip($attributeIds));
270-
}
271-
272-
/**
283+
* Filter values
284+
*
273285
* @param array $attributeValuesList
274286
* @param array $assignedAttributeValuesId
275287
* @return array

ConfigurableProductDataExporter/Model/Query/ProductOptionQuery.php

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ public function __construct(
3838
*/
3939
public function getQuery(array $arguments): Select
4040
{
41-
$productIds = isset($arguments['productId']) ? $arguments['productId'] : [];
42-
$storeViewCodes = isset($arguments['storeViewCode']) ? $arguments['storeViewCode'] : [];
41+
$productIds = $arguments['productId'] ?? [];
42+
$storeViewCodes = $arguments['storeViewCode'] ?? [];
4343
$connection = $this->resourceConnection->getConnection();
4444
$joinField = $connection->getAutoIncrementField(
4545
$this->resourceConnection->getTableName('catalog_product_entity')
@@ -60,6 +60,10 @@ public function getQuery(array $arguments): Select
6060
['eav' => $this->resourceConnection->getTableName('eav_attribute')],
6161
'eav.attribute_id = super_attribute.attribute_id',
6262
['attribute_code' => 'eav.attribute_code']
63+
)->join(
64+
['cea' => $this->resourceConnection->getTableName('catalog_eav_attribute')],
65+
'cea.attribute_id = super_attribute.attribute_id',
66+
['swatchType' => 'cea.additional_data']
6367
)->join(
6468
['product_website' => $this->resourceConnection->getTableName('catalog_product_website')],
6569
'product_website.product_id = product.entity_id',
@@ -71,19 +75,6 @@ public function getQuery(array $arguments): Select
7175
. ' AND s.website_id = product_website.website_id'
7276
: 's.store_id != 0' . ' AND s.website_id = product_website.website_id',
7377
['storeViewCode' => 's.code']
74-
)->join(
75-
['configurable_link' => $this->resourceConnection->getTableName('catalog_product_super_link')],
76-
sprintf('configurable_link.parent_id = product.%s', $joinField),
77-
[]
78-
)->join(
79-
['catalog_product_entity' => $this->resourceConnection->getTableName('catalog_product_entity')],
80-
'catalog_product_entity.entity_id = configurable_link.product_id',
81-
[]
82-
)->join(
83-
['super_attribute_value' => $this->resourceConnection->getTableName('catalog_product_entity_int')],
84-
sprintf('super_attribute_value.%s = catalog_product_entity.%s', $joinField, $joinField)
85-
. ' AND super_attribute_value.attribute_id = super_attribute.attribute_id',
86-
['attributeValues' => 'GROUP_CONCAT(DISTINCT super_attribute_value.value)']
8778
)
8879
->joinLeft(
8980
['attr_label' => $this->resourceConnection->getTableName('eav_attribute_label')],
@@ -96,8 +87,7 @@ public function getQuery(array $arguments): Select
9687
),
9788
]
9889
)
99-
->where('product.entity_id IN (?)', $productIds)
100-
->group(['product.entity_id', 's.store_id', 'eav.attribute_code']);
90+
->where('product.entity_id IN (?)', $productIds);
10191
return $select;
10292
}
10393
}

ConfigurableProductDataExporter/Model/Query/ProductOptionValueQuery.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,19 @@ public function getQuery(array $arguments): Select
6767
'label' => new Expression('CASE WHEN ovs.value IS NULL THEN ovd.value ELSE ovs.value END'),
6868
]
6969
)
70+
->joinLeft(
71+
['aod' => $this->resourceConnection->getTableName('eav_attribute_option_swatch')],
72+
'aod.option_id = eao.option_id AND aod.store_id = 0',
73+
[]
74+
)
75+
->joinLeft(
76+
['aos' => $this->resourceConnection->getTableName('eav_attribute_option_swatch')],
77+
'aos.option_id = eao.option_id AND aos.store_id = s.store_id',
78+
[
79+
'swatchValue' => new Expression('CASE WHEN aos.value IS NULL THEN aod.value ELSE aos.value END'),
80+
'swatchType' => new Expression('CASE WHEN aos.value IS NULL THEN aod.type ELSE aos.type END'),
81+
]
82+
)
7083
->where('eao.attribute_id IN (?)', $attributeIds)
7184
->where('s.code IN (?)', $storeViewCodes);
7285
return $select;

ConfigurableProductDataExporter/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"php": "~8.1.0||~8.2.0",
1919
"magento/framework": ">=103.0.4",
2020
"magento/module-configurable-product": ">=100.4.4",
21+
"magento/module-swatches": ">=100.4.4",
2122
"magento/module-catalog": ">=104.0.4",
2223
"magento/module-eav": ">=102.1.4",
2324
"magento/module-store": ">=101.1.4",

0 commit comments

Comments
 (0)