88namespace Magento \ConfigurableProductDataExporter \Model \Provider \Product ;
99
1010use Exception ;
11- use Generator ;
1211use Magento \CatalogDataExporter \Model \Provider \Product \OptionProviderInterface ;
1312use Magento \ConfigurableProduct \Model \Product \Type \Configurable ;
1413use Magento \ConfigurableProductDataExporter \Model \Query \ProductOptionQuery ;
1514use Magento \ConfigurableProductDataExporter \Model \Query \ProductOptionValueQuery ;
1615use 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 ;
2116use 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 */
2725class 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
0 commit comments