Skip to content

Commit 1f6c16d

Browse files
Valeriy NaydaYaroslav Onischenko
authored andcommitted
MAGETWO-66515: [Performance] Reduce queries to stock_status_index on Category Page
-- fix static tests -- fixes after CR
1 parent b7a8b32 commit 1f6c16d

File tree

1 file changed

+213
-81
lines changed

1 file changed

+213
-81
lines changed

app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php

Lines changed: 213 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
use Magento\Catalog\Api\Data\ProductAttributeInterface;
99
use Magento\Catalog\Api\Data\ProductInterface;
10+
use Magento\Catalog\Api\Data\ProductInterfaceFactory;
1011
use Magento\Catalog\Api\ProductRepositoryInterface;
1112
use Magento\ConfigurableProduct\Model\Product\Type\Collection\SalableProcessor;
1213
use Magento\Catalog\Model\Config;
@@ -72,6 +73,13 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType
7273
*/
7374
protected $_usedProducts = '_cache_instance_products';
7475

76+
/**
77+
* Cache key for salable used products
78+
*
79+
* @var string
80+
*/
81+
private $usedSalableProducts = '_cache_instance_salable_products';
82+
7583
/**
7684
* Product is composite
7785
*
@@ -164,6 +172,20 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType
164172
*/
165173
private $customerSession;
166174

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+
167189
/**
168190
* @codingStandardsIgnoreStart/End
169191
*
@@ -185,6 +207,8 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType
185207
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
186208
* @param \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface $extensionAttributesJoinProcessor
187209
* @param \Magento\Framework\Serialize\Serializer\Json $serializer
210+
* @param ProductInterfaceFactory $productFactory
211+
* @param SalableProcessor $salableProcessor
188212
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
189213
*/
190214
public function __construct(
@@ -208,6 +232,7 @@ public function __construct(
208232
\Magento\Framework\Cache\FrontendInterface $cache = null,
209233
\Magento\Customer\Model\Session $customerSession = null,
210234
\Magento\Framework\Serialize\Serializer\Json $serializer = null,
235+
ProductInterfaceFactory $productFactory = null,
211236
SalableProcessor $salableProcessor = null
212237
) {
213238
$this->typeConfigurableFactory = $typeConfigurableFactory;
@@ -221,6 +246,8 @@ public function __construct(
221246
$this->cache = $cache;
222247
$this->customerSession = $customerSession;
223248
$this->salableProcessor = $salableProcessor ?: ObjectManager::getInstance()->get(SalableProcessor::class);
249+
$this->productFactory = $productFactory ?: ObjectManager::getInstance()
250+
->get(ProductInterfaceFactory::class);
224251
parent::__construct(
225252
$catalogProductOption,
226253
$eavConfig,
@@ -503,87 +530,6 @@ public function getUsedProductIds($product)
503530
return $product->getData($this->_usedProductIds);
504531
}
505532

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-
587533
/**
588534
* Retrieve GalleryReadHandler
589535
*
@@ -1262,4 +1208,190 @@ public function isPossibleBuyFromList($product)
12621208

12631209
return $isAllCustomOptionsDisplayed;
12641210
}
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+
}
12651397
}

0 commit comments

Comments
 (0)