@@ -73,6 +73,13 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType
73
73
*/
74
74
protected $ _usedProducts = '_cache_instance_products ' ;
75
75
76
+ /**
77
+ * Cache key for salable used products
78
+ *
79
+ * @var string
80
+ */
81
+ private $ usedSalableProducts = '_cache_instance_salable_products ' ;
82
+
76
83
/**
77
84
* Product is composite
78
85
*
@@ -166,10 +173,19 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType
166
173
private $ customerSession ;
167
174
168
175
/**
176
+ * Product factory
177
+ *
169
178
* @var ProductInterfaceFactory
170
179
*/
171
180
private $ productFactory ;
172
181
182
+ /**
183
+ * Collection salable processor
184
+ *
185
+ * @var SalableProcessor
186
+ */
187
+ private $ salableProcessor ;
188
+
173
189
/**
174
190
* @codingStandardsIgnoreStart/End
175
191
*
@@ -192,6 +208,7 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType
192
208
* @param \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface $extensionAttributesJoinProcessor
193
209
* @param \Magento\Framework\Serialize\Serializer\Json $serializer
194
210
* @param ProductInterfaceFactory $productFactory
211
+ * @param SalableProcessor $salableProcessor
195
212
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
196
213
*/
197
214
public function __construct (
@@ -513,88 +530,6 @@ public function getUsedProductIds($product)
513
530
return $ product ->getData ($ this ->_usedProductIds );
514
531
}
515
532
516
- /**
517
- * Retrieve array of "subproducts"
518
- *
519
- * @param \Magento\Catalog\Model\Product $product
520
- * @param array $requiredAttributeIds
521
- * @return array
522
- */
523
- public function getUsedProducts ($ product , $ requiredAttributeIds = null )
524
- {
525
- \Magento \Framework \Profiler::start (
526
- 'CONFIGURABLE: ' . __METHOD__ ,
527
- ['group ' => 'CONFIGURABLE ' , 'method ' => __METHOD__ ]
528
- );
529
- if (!$ product ->hasData ($ this ->_usedProducts )) {
530
- $ metadata = $ this ->getMetadataPool ()->getMetadata (ProductInterface::class);
531
- $ productId = $ product ->getData ($ metadata ->getLinkField ());
532
-
533
- $ key = md5 (
534
- implode (
535
- '_ ' ,
536
- [
537
- __METHOD__ ,
538
- $ productId ,
539
- $ product ->getStoreId (),
540
- $ this ->getCustomerSession ()->getCustomerGroupId (),
541
- json_encode ($ requiredAttributeIds )
542
- ]
543
- )
544
- );
545
- $ data = $ this ->serializer ->unserialize ($ this ->getCache ()->load ($ key ));
546
- if (!empty ($ data )) {
547
- $ usedProducts = [];
548
- foreach ($ data as $ item ) {
549
- $ productItem = $ this ->productFactory ->create ();
550
- $ productItem ->setData ($ item );
551
- $ usedProducts [] = $ productItem ;
552
- }
553
- } else {
554
- $ collection = $ this ->getUsedProductCollection ($ product );
555
- $ collection
556
- ->setFlag ('has_stock_status_filter ' , true )
557
- ->addAttributeToSelect ($ this ->getCatalogConfig ()->getProductAttributes ())
558
- ->addFilterByRequiredOptions ()
559
- ->setStoreId ($ product ->getStoreId ());
560
-
561
- $ requiredAttributes = ['name ' , 'price ' , 'weight ' , 'image ' , 'thumbnail ' , 'status ' , 'media_gallery ' ];
562
- foreach ($ requiredAttributes as $ attributeCode ) {
563
- $ collection ->addAttributeToSelect ($ attributeCode );
564
- }
565
- foreach ($ this ->getUsedProductAttributes ($ product ) as $ usedProductAttribute ) {
566
- $ collection ->addAttributeToSelect ($ usedProductAttribute ->getAttributeCode ());
567
- }
568
- $ collection ->addMediaGalleryData ();
569
- $ collection ->addTierPriceData ();
570
- $ usedProducts = $ collection ->getItems ();
571
-
572
- $ this ->getCache ()->save (
573
- $ this ->serializer ->serialize (array_map (
574
- function ($ item ) {
575
- return $ item ->getData ();
576
- },
577
- $ usedProducts
578
- )),
579
- $ key ,
580
- array_merge (
581
- $ product ->getIdentities (),
582
- [
583
- \Magento \Catalog \Model \Category::CACHE_TAG ,
584
- \Magento \Catalog \Model \Product::CACHE_TAG ,
585
- 'price ' ,
586
- self ::TYPE_CODE . '_ ' . $ productId
587
- ]
588
- )
589
- );
590
- }
591
- $ product ->setData ($ this ->_usedProducts , $ usedProducts );
592
- }
593
- \Magento \Framework \Profiler::stop ('CONFIGURABLE: ' . __METHOD__ );
594
- $ usedProducts = $ product ->getData ($ this ->_usedProducts );
595
- return $ usedProducts ;
596
- }
597
-
598
533
/**
599
534
* Retrieve GalleryReadHandler
600
535
*
@@ -1273,4 +1208,186 @@ public function isPossibleBuyFromList($product)
1273
1208
1274
1209
return $ isAllCustomOptionsDisplayed ;
1275
1210
}
1211
+
1212
+ /**
1213
+ * Returns array of sub-products for specified configurable product
1214
+ *
1215
+ * Result array contains all children for specified configurable product
1216
+ *
1217
+ * @param \Magento\Catalog\Model\Product $product
1218
+ * @param array $requiredAttributeIds
1219
+ * @return ProductInterface[]
1220
+ */
1221
+ public function getUsedProducts ($ product , $ requiredAttributeIds = null )
1222
+ {
1223
+ $ metadata = $ this ->getMetadataPool ()->getMetadata (ProductInterface::class);
1224
+ $ keyParts = [
1225
+ __METHOD__ ,
1226
+ $ product ->getData ($ metadata ->getLinkField ()),
1227
+ $ product ->getStoreId (),
1228
+ $ this ->getCustomerSession ()->getCustomerGroupId (),
1229
+ $ requiredAttributeIds
1230
+ ];
1231
+ $ cacheKey = $ this ->getUsedProductsCacheKey ($ keyParts );
1232
+ return $ this ->loadUsedProducts ($ product , $ cacheKey );
1233
+ }
1234
+
1235
+ /**
1236
+ * Returns array of sub-products for specified configurable product filtered by salable status
1237
+ *
1238
+ * Result array contains only those children for specified configurable product which are salable on store front
1239
+ *
1240
+ * @deprecated Not used anymore. Keep it for backward compatibility.
1241
+ *
1242
+ * @param \Magento\Catalog\Model\Product $product
1243
+ * @param array|null $requiredAttributeIds
1244
+ * @return ProductInterface[]
1245
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
1246
+ */
1247
+ public function getSalableUsedProducts (\Magento \Catalog \Model \Product $ product , $ requiredAttributeIds = null )
1248
+ {
1249
+ $ metadata = $ this ->getMetadataPool ()->getMetadata (ProductInterface::class);
1250
+ $ keyParts = [
1251
+ __METHOD__ ,
1252
+ $ product ->getData ($ metadata ->getLinkField ()),
1253
+ $ product ->getStoreId (),
1254
+ $ this ->getCustomerSession ()->getCustomerGroupId ()
1255
+ ];
1256
+ $ cacheKey = $ this ->getUsedProductsCacheKey ($ keyParts );
1257
+
1258
+ return $ this ->loadUsedProducts ($ product , $ cacheKey , true );
1259
+ }
1260
+
1261
+ /**
1262
+ * Load collection on sub-products for specified configurable product
1263
+ *
1264
+ * Load collection of sub-products, apply result to specified configurable product and store result to cache
1265
+ * Number of loaded sub-products depends on $salableOnly parameter
1266
+ * $salableOnly = true - result array contains only salable sub-products
1267
+ * $salableOnly = false - result array contains all sub-products
1268
+ * $cacheKey - allow store result data in different cache records
1269
+ *
1270
+ * @param \Magento\Catalog\Model\Product $product
1271
+ * @param string $cacheKey
1272
+ * @param bool $salableOnly
1273
+ * @return ProductInterface[]
1274
+ */
1275
+ private function loadUsedProducts (\Magento \Catalog \Model \Product $ product , $ cacheKey , $ salableOnly = false )
1276
+ {
1277
+ $ dataFieldName = $ salableOnly ? $ this ->usedSalableProducts : $ this ->_usedProducts ;
1278
+ if (!$ product ->hasData ($ dataFieldName )) {
1279
+ $ usedProducts = $ this ->readUsedProductsCacheData ($ cacheKey );
1280
+ if ($ usedProducts === null ) {
1281
+ $ collection = $ this ->getConfiguredUsedProductCollection ($ product );
1282
+ if ($ salableOnly ) {
1283
+ $ collection = $ this ->salableProcessor ->process ($ collection );
1284
+ }
1285
+ $ usedProducts = $ collection ->getItems ();
1286
+ $ this ->saveUsedProductsCacheData ($ product , $ usedProducts , $ cacheKey );
1287
+ }
1288
+ $ product ->setData ($ dataFieldName , $ usedProducts );
1289
+ }
1290
+
1291
+ return $ product ->getData ($ dataFieldName );
1292
+ }
1293
+
1294
+ /**
1295
+ * Read used products data from cache
1296
+ *
1297
+ * Looking for cache record stored under provided $cacheKey
1298
+ * In case data exists turns it into array of products
1299
+ *
1300
+ * @param string $cacheKey
1301
+ * @return ProductInterface[]|null
1302
+ */
1303
+ private function readUsedProductsCacheData ($ cacheKey )
1304
+ {
1305
+ $ usedProducts = null ;
1306
+ $ data = $ this ->serializer ->unserialize ($ this ->getCache ()->load ($ cacheKey ));
1307
+ if (!empty ($ data )) {
1308
+ $ usedProducts = [];
1309
+ foreach ($ data as $ item ) {
1310
+ $ productItem = $ this ->productFactory ->create ();
1311
+ $ productItem ->setData ($ item );
1312
+ $ usedProducts [] = $ productItem ;
1313
+ }
1314
+ }
1315
+
1316
+ return $ usedProducts ;
1317
+ }
1318
+
1319
+ /**
1320
+ * Save $subProducts to cache record identified with provided $cacheKey
1321
+ *
1322
+ * Cached data will be tagged with combined list of product tags and data specific tags i.e. 'price' etc.
1323
+ *
1324
+ * @param \Magento\Catalog\Model\Product $product
1325
+ * @param ProductInterface[] $subProducts
1326
+ * @param string $cacheKey
1327
+ * @return bool
1328
+ */
1329
+ private function saveUsedProductsCacheData (\Magento \Catalog \Model \Product $ product , array $ subProducts , $ cacheKey )
1330
+ {
1331
+ $ metadata = $ this ->getMetadataPool ()->getMetadata (ProductInterface::class);
1332
+ return $ this ->getCache ()->save (
1333
+ $ this ->serializer ->serialize (array_map (
1334
+ function ($ item ) {
1335
+ return $ item ->getData ();
1336
+ },
1337
+ $ subProducts
1338
+ )),
1339
+ $ cacheKey ,
1340
+ array_merge (
1341
+ $ product ->getIdentities (),
1342
+ [
1343
+ \Magento \Catalog \Model \Category::CACHE_TAG ,
1344
+ \Magento \Catalog \Model \Product::CACHE_TAG ,
1345
+ 'price ' ,
1346
+ self ::TYPE_CODE . '_ ' . $ product ->getData ($ metadata ->getLinkField ())
1347
+ ]
1348
+ )
1349
+ );
1350
+ }
1351
+
1352
+ /**
1353
+ * Create string key based on $keyParts
1354
+ *
1355
+ * $keyParts - one dimensional array of strings
1356
+ *
1357
+ * @param array $keyParts
1358
+ * @return string
1359
+ */
1360
+ private function getUsedProductsCacheKey ($ keyParts )
1361
+ {
1362
+ return md5 (implode ('_ ' , $ keyParts ));
1363
+ }
1364
+
1365
+ /**
1366
+ * Prepare collection for retrieving sub-products of specified configurable product
1367
+ *
1368
+ * Retrieve related products collection with additional configuration
1369
+ *
1370
+ * @param \Magento\Catalog\Model\Product $product
1371
+ * @return \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection
1372
+ */
1373
+ private function getConfiguredUsedProductCollection (\Magento \Catalog \Model \Product $ product )
1374
+ {
1375
+ $ collection = $ this ->getUsedProductCollection ($ product );
1376
+ $ collection
1377
+ ->setFlag ('has_stock_status_filter ' , true )
1378
+ ->addAttributeToSelect ($ this ->getCatalogConfig ()->getProductAttributes ())
1379
+ ->addFilterByRequiredOptions ()
1380
+ ->setStoreId ($ product ->getStoreId ());
1381
+
1382
+ $ requiredAttributes = ['name ' , 'price ' , 'weight ' , 'image ' , 'thumbnail ' , 'status ' , 'media_gallery ' ];
1383
+ foreach ($ requiredAttributes as $ attributeCode ) {
1384
+ $ collection ->addAttributeToSelect ($ attributeCode );
1385
+ }
1386
+ foreach ($ this ->getUsedProductAttributes ($ product ) as $ usedProductAttribute ) {
1387
+ $ collection ->addAttributeToSelect ($ usedProductAttribute ->getAttributeCode ());
1388
+ }
1389
+ $ collection ->addMediaGalleryData ();
1390
+ $ collection ->addTierPriceData ();
1391
+ return $ collection ;
1392
+ }
1276
1393
}
0 commit comments