7
7
8
8
use Magento \Catalog \Api \Data \ProductAttributeInterface ;
9
9
use Magento \Catalog \Api \Data \ProductInterface ;
10
+ use Magento \Catalog \Api \Data \ProductInterfaceFactory ;
10
11
use Magento \Catalog \Api \ProductRepositoryInterface ;
11
12
use Magento \ConfigurableProduct \Model \Product \Type \Collection \SalableProcessor ;
12
13
use Magento \Catalog \Model \Config ;
@@ -72,6 +73,13 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType
72
73
*/
73
74
protected $ _usedProducts = '_cache_instance_products ' ;
74
75
76
+ /**
77
+ * Cache key for salable used products
78
+ *
79
+ * @var string
80
+ */
81
+ private $ usedSalableProducts = '_cache_instance_salable_products ' ;
82
+
75
83
/**
76
84
* Product is composite
77
85
*
@@ -164,6 +172,20 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType
164
172
*/
165
173
private $ customerSession ;
166
174
175
+ /**
176
+ * Product factory
177
+ *
178
+ * @var ProductInterfaceFactory
179
+ */
180
+ private $ productFactory ;
181
+
182
+ /**
183
+ * Collection salable processor
184
+ *
185
+ * @var SalableProcessor
186
+ */
187
+ private $ salableProcessor ;
188
+
167
189
/**
168
190
* @codingStandardsIgnoreStart/End
169
191
*
@@ -185,6 +207,8 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType
185
207
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
186
208
* @param \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface $extensionAttributesJoinProcessor
187
209
* @param \Magento\Framework\Serialize\Serializer\Json $serializer
210
+ * @param ProductInterfaceFactory $productFactory
211
+ * @param SalableProcessor $salableProcessor
188
212
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
189
213
*/
190
214
public function __construct (
@@ -208,6 +232,7 @@ public function __construct(
208
232
\Magento \Framework \Cache \FrontendInterface $ cache = null ,
209
233
\Magento \Customer \Model \Session $ customerSession = null ,
210
234
\Magento \Framework \Serialize \Serializer \Json $ serializer = null ,
235
+ ProductInterfaceFactory $ productFactory = null ,
211
236
SalableProcessor $ salableProcessor = null
212
237
) {
213
238
$ this ->typeConfigurableFactory = $ typeConfigurableFactory ;
@@ -221,6 +246,8 @@ public function __construct(
221
246
$ this ->cache = $ cache ;
222
247
$ this ->customerSession = $ customerSession ;
223
248
$ this ->salableProcessor = $ salableProcessor ?: ObjectManager::getInstance ()->get (SalableProcessor::class);
249
+ $ this ->productFactory = $ productFactory ?: ObjectManager::getInstance ()
250
+ ->get (ProductInterfaceFactory::class);
224
251
parent ::__construct (
225
252
$ catalogProductOption ,
226
253
$ eavConfig ,
@@ -503,87 +530,6 @@ public function getUsedProductIds($product)
503
530
return $ product ->getData ($ this ->_usedProductIds );
504
531
}
505
532
506
- /**
507
- * Retrieve array of "subproducts"
508
- *
509
- * @param \Magento\Catalog\Model\Product $product
510
- * @param array $requiredAttributeIds
511
- * @return array
512
- */
513
- public function getUsedProducts ($ product , $ requiredAttributeIds = null )
514
- {
515
- \Magento \Framework \Profiler::start (
516
- 'CONFIGURABLE: ' . __METHOD__ ,
517
- ['group ' => 'CONFIGURABLE ' , 'method ' => __METHOD__ ]
518
- );
519
- if (!$ product ->hasData ($ this ->_usedProducts )) {
520
- $ metadata = $ this ->getMetadataPool ()->getMetadata (ProductInterface::class);
521
- $ productId = $ product ->getData ($ metadata ->getLinkField ());
522
-
523
- $ key = md5 (
524
- implode (
525
- '_ ' ,
526
- [
527
- __METHOD__ ,
528
- $ productId ,
529
- $ product ->getStoreId (),
530
- $ this ->getCustomerSession ()->getCustomerGroupId (),
531
- json_encode ($ requiredAttributeIds )
532
- ]
533
- )
534
- );
535
- $ collection = $ this ->getUsedProductCollection ($ product );
536
- $ data = $ this ->serializer ->unserialize ($ this ->getCache ()->load ($ key ));
537
- if (!empty ($ data )) {
538
- $ usedProducts = [];
539
- foreach ($ data as $ item ) {
540
- $ productItem = $ collection ->getNewEmptyItem ()->setData ($ item );
541
- $ usedProducts [] = $ productItem ;
542
- }
543
- } else {
544
- $ collection
545
- ->setFlag ('has_stock_status_filter ' , true )
546
- ->addAttributeToSelect ($ this ->getCatalogConfig ()->getProductAttributes ())
547
- ->addFilterByRequiredOptions ()
548
- ->setStoreId ($ product ->getStoreId ());
549
-
550
- $ requiredAttributes = ['name ' , 'price ' , 'weight ' , 'image ' , 'thumbnail ' , 'status ' , 'media_gallery ' ];
551
- foreach ($ requiredAttributes as $ attributeCode ) {
552
- $ collection ->addAttributeToSelect ($ attributeCode );
553
- }
554
- foreach ($ this ->getUsedProductAttributes ($ product ) as $ usedProductAttribute ) {
555
- $ collection ->addAttributeToSelect ($ usedProductAttribute ->getAttributeCode ());
556
- }
557
- $ collection ->addMediaGalleryData ();
558
- $ collection ->addTierPriceData ();
559
- $ usedProducts = $ collection ->getItems ();
560
-
561
- $ this ->getCache ()->save (
562
- $ this ->serializer ->serialize (array_map (
563
- function ($ item ) {
564
- return $ item ->getData ();
565
- },
566
- $ usedProducts
567
- )),
568
- $ key ,
569
- array_merge (
570
- $ product ->getIdentities (),
571
- [
572
- \Magento \Catalog \Model \Category::CACHE_TAG ,
573
- \Magento \Catalog \Model \Product::CACHE_TAG ,
574
- 'price ' ,
575
- self ::TYPE_CODE . '_ ' . $ productId
576
- ]
577
- )
578
- );
579
- }
580
- $ product ->setData ($ this ->_usedProducts , $ usedProducts );
581
- }
582
- \Magento \Framework \Profiler::stop ('CONFIGURABLE: ' . __METHOD__ );
583
- $ usedProducts = $ product ->getData ($ this ->_usedProducts );
584
- return $ usedProducts ;
585
- }
586
-
587
533
/**
588
534
* Retrieve GalleryReadHandler
589
535
*
@@ -1262,4 +1208,190 @@ public function isPossibleBuyFromList($product)
1262
1208
1263
1209
return $ isAllCustomOptionsDisplayed ;
1264
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 (\Magento \Catalog \Model \Product $ 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[]|null
1274
+ */
1275
+ private function loadUsedProducts (\Magento \Catalog \Model \Product $ product , $ cacheKey , $ salableOnly = false )
1276
+ {
1277
+ \Magento \Framework \Profiler::start (
1278
+ 'CONFIGURABLE: ' . __METHOD__ ,
1279
+ ['group ' => 'CONFIGURABLE ' , 'method ' => __METHOD__ ]
1280
+ );
1281
+ if (!$ product ->hasData ($ this ->usedSalableProducts )) {
1282
+ $ usedProducts = $ this ->readUsedProductsCacheData ($ cacheKey );
1283
+ if ($ usedProducts === null ) {
1284
+ $ collection = $ this ->getConfiguredUsedProductCollection ($ product );
1285
+ if ($ salableOnly ) {
1286
+ $ collection = $ this ->salableProcessor ->process ($ collection );
1287
+ }
1288
+ $ usedProducts = $ collection ->getItems ();
1289
+ $ this ->saveUsedProductsCacheData ($ product , $ usedProducts , $ cacheKey );
1290
+ }
1291
+ $ product ->setData ($ this ->usedSalableProducts , $ usedProducts );
1292
+ }
1293
+ \Magento \Framework \Profiler::stop ('CONFIGURABLE: ' . __METHOD__ );
1294
+
1295
+ return $ product ->getData ($ this ->usedSalableProducts );
1296
+ }
1297
+
1298
+ /**
1299
+ * Read used products data from cache
1300
+ *
1301
+ * Looking for cache record stored under provided $cacheKey
1302
+ * In case data exists turns it into array of products
1303
+ *
1304
+ * @param string $cacheKey
1305
+ * @return ProductInterface[]|null
1306
+ */
1307
+ private function readUsedProductsCacheData ($ cacheKey )
1308
+ {
1309
+ $ usedProducts = null ;
1310
+ $ data = $ this ->serializer ->unserialize ($ this ->getCache ()->load ($ cacheKey ));
1311
+ if (!empty ($ data )) {
1312
+ $ usedProducts = [];
1313
+ foreach ($ data as $ item ) {
1314
+ $ productItem = $ this ->productFactory ->create ();
1315
+ $ productItem ->setData ($ item );
1316
+ $ usedProducts [] = $ productItem ;
1317
+ }
1318
+ }
1319
+
1320
+ return $ usedProducts ;
1321
+ }
1322
+
1323
+ /**
1324
+ * Save $subProducts to cache record identified with provided $cacheKey
1325
+ *
1326
+ * Cached data will be tagged with combined list of product tags and data specific tags i.e. 'price' etc.
1327
+ *
1328
+ * @param \Magento\Catalog\Model\Product $product
1329
+ * @param ProductInterface[] $subProducts
1330
+ * @param string $cacheKey
1331
+ * @return bool
1332
+ */
1333
+ private function saveUsedProductsCacheData (\Magento \Catalog \Model \Product $ product , array $ subProducts , $ cacheKey )
1334
+ {
1335
+ $ metadata = $ this ->getMetadataPool ()->getMetadata (ProductInterface::class);
1336
+ return $ this ->getCache ()->save (
1337
+ $ this ->serializer ->serialize (array_map (
1338
+ function ($ item ) {
1339
+ return $ item ->getData ();
1340
+ },
1341
+ $ subProducts
1342
+ )),
1343
+ $ cacheKey ,
1344
+ array_merge (
1345
+ $ product ->getIdentities (),
1346
+ [
1347
+ \Magento \Catalog \Model \Category::CACHE_TAG ,
1348
+ \Magento \Catalog \Model \Product::CACHE_TAG ,
1349
+ 'price ' ,
1350
+ self ::TYPE_CODE . '_ ' . $ product ->getData ($ metadata ->getLinkField ())
1351
+ ]
1352
+ )
1353
+ );
1354
+ }
1355
+
1356
+ /**
1357
+ * Create string key based on $keyParts
1358
+ *
1359
+ * $keyParts - one dimensional array of strings
1360
+ *
1361
+ * @param array $keyParts
1362
+ * @return string
1363
+ */
1364
+ private function getUsedProductsCacheKey ($ keyParts )
1365
+ {
1366
+ return md5 (implode ('_ ' , $ keyParts ));
1367
+ }
1368
+
1369
+ /**
1370
+ * Get configured used collection
1371
+ *
1372
+ * Retrieve related products collection with additional configuration
1373
+ *
1374
+ * @param \Magento\Catalog\Model\Product $product
1375
+ * @return \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection
1376
+ */
1377
+ private function getConfiguredUsedProductCollection (\Magento \Catalog \Model \Product $ product )
1378
+ {
1379
+ $ collection = $ this ->getUsedProductCollection ($ product );
1380
+ $ collection
1381
+ ->setFlag ('has_stock_status_filter ' , true )
1382
+ ->addAttributeToSelect ($ this ->getCatalogConfig ()->getProductAttributes ())
1383
+ ->addFilterByRequiredOptions ()
1384
+ ->setStoreId ($ product ->getStoreId ());
1385
+
1386
+ $ requiredAttributes = ['name ' , 'price ' , 'weight ' , 'image ' , 'thumbnail ' , 'status ' , 'media_gallery ' ];
1387
+ foreach ($ requiredAttributes as $ attributeCode ) {
1388
+ $ collection ->addAttributeToSelect ($ attributeCode );
1389
+ }
1390
+ foreach ($ this ->getUsedProductAttributes ($ product ) as $ usedProductAttribute ) {
1391
+ $ collection ->addAttributeToSelect ($ usedProductAttribute ->getAttributeCode ());
1392
+ }
1393
+ $ collection ->addMediaGalleryData ();
1394
+ $ collection ->addTierPriceData ();
1395
+ return $ collection ;
1396
+ }
1265
1397
}
0 commit comments