diff --git a/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/UsedProductsCache.php b/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/UsedProductsCache.php
new file mode 100644
index 0000000000000..69c0a5f6000f0
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/UsedProductsCache.php
@@ -0,0 +1,185 @@
+metadataPool = $metadataPool;
+ $this->cache = $cache;
+ $this->serializer = $serializer;
+ $this->productFactory = $productFactory;
+ $this->customerSession = $customerSession;
+ }
+
+ /**
+ * Retrieve used products for configurable product
+ *
+ * @param Configurable $subject
+ * @param callable $proceed
+ * @param Product $product
+ * @param array|null $requiredAttributeIds
+ * @return ProductInterface[]
+ */
+ public function aroundGetUsedProducts(
+ Configurable $subject,
+ callable $proceed,
+ $product,
+ $requiredAttributeIds = null
+ ) {
+ $cacheKey = $this->getCacheKey($product, $requiredAttributeIds);
+ $usedProducts = $this->readUsedProductsCacheData($cacheKey);
+ if ($usedProducts === null) {
+ $usedProducts = $proceed($product, $requiredAttributeIds);
+ $this->saveUsedProductsCacheData($product, $usedProducts, $cacheKey);
+ }
+
+ return $usedProducts;
+ }
+
+ /**
+ * Generate cache key for product
+ *
+ * @param Product $product
+ * @param array|null $requiredAttributeIds
+ * @return string
+ */
+ private function getCacheKey($product, $requiredAttributeIds = null): string
+ {
+ $metadata = $this->metadataPool->getMetadata(ProductInterface::class);
+ $keyParts = [
+ 'getUsedProducts',
+ $product->getData($metadata->getLinkField()),
+ $product->getStoreId(),
+ $this->customerSession->getCustomerGroupId(),
+ ];
+ if ($requiredAttributeIds !== null) {
+ sort($requiredAttributeIds);
+ $keyParts[] = implode('', $requiredAttributeIds);
+ }
+ $cacheKey = sha1(implode('_', $keyParts));
+
+ return $cacheKey;
+ }
+
+ /**
+ * Read used products data from cache
+ *
+ * Looking for cache record stored under provided $cacheKey
+ * In case data exists turns it into array of products
+ *
+ * @param string $cacheKey
+ * @return ProductInterface[]|null
+ */
+ private function readUsedProductsCacheData(string $cacheKey): ?array
+ {
+ $data = $this->cache->load($cacheKey);
+ if (!$data) {
+ return null;
+ }
+
+ $items = $this->serializer->unserialize($data);
+ if (!$items) {
+ return null;
+ }
+
+ $usedProducts = [];
+ foreach ($items as $item) {
+ /** @var Product $productItem */
+ $productItem = $this->productFactory->create();
+ $productItem->setData($item);
+ $usedProducts[] = $productItem;
+ }
+
+ return $usedProducts;
+ }
+
+ /**
+ * Save $subProducts to cache record identified with provided $cacheKey
+ *
+ * Cached data will be tagged with combined list of product tags and data specific tags i.e. 'price' etc.
+ *
+ * @param Product $product
+ * @param ProductInterface[] $subProducts
+ * @param string $cacheKey
+ * @return bool
+ */
+ private function saveUsedProductsCacheData(Product $product, array $subProducts, string $cacheKey): bool
+ {
+ $metadata = $this->metadataPool->getMetadata(ProductInterface::class);
+ $data = $this->serializer->serialize(array_map(
+ function ($item) {
+ return $item->getData();
+ },
+ $subProducts
+ ));
+ $tags = array_merge(
+ $product->getIdentities(),
+ [
+ Category::CACHE_TAG,
+ Product::CACHE_TAG,
+ 'price',
+ Configurable::TYPE_CODE . '_' . $product->getData($metadata->getLinkField())
+ ]
+ );
+ $result = $this->cache->save($data, $cacheKey, $tags);
+
+ return (bool) $result;
+ }
+}
diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php
index 77450748f7eba..c60953e33e9eb 100644
--- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php
+++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php
@@ -1233,28 +1233,22 @@ public function isPossibleBuyFromList($product)
* Returns array of sub-products for specified configurable product
*
* $requiredAttributeIds - one dimensional array, if provided
- *
* Result array contains all children for specified configurable product
*
- * @param \Magento\Catalog\Model\Product $product
- * @param array $requiredAttributeIds
+ * @param \Magento\Catalog\Model\Product $product
+ * @param array $requiredAttributeIds
* @return ProductInterface[]
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function getUsedProducts($product, $requiredAttributeIds = null)
{
- $metadata = $this->getMetadataPool()->getMetadata(ProductInterface::class);
- $keyParts = [
- __METHOD__,
- $product->getData($metadata->getLinkField()),
- $product->getStoreId(),
- $this->getCustomerSession()->getCustomerGroupId()
- ];
- if ($requiredAttributeIds !== null) {
- sort($requiredAttributeIds);
- $keyParts[] = implode('', $requiredAttributeIds);
+ if (!$product->hasData($this->_usedProducts)) {
+ $collection = $this->getConfiguredUsedProductCollection($product, false);
+ $usedProducts = array_values($collection->getItems());
+ $product->setData($this->_usedProducts, $usedProducts);
}
- $cacheKey = $this->getUsedProductsCacheKey($keyParts);
- return $this->loadUsedProducts($product, $cacheKey);
+
+ return $product->getData($this->_usedProducts);
}
/**
@@ -1304,11 +1298,15 @@ private function loadUsedProducts(\Magento\Catalog\Model\Product $product, $cach
{
$dataFieldName = $salableOnly ? $this->usedSalableProducts : $this->_usedProducts;
if (!$product->hasData($dataFieldName)) {
- $collection = $this->getConfiguredUsedProductCollection($product, false);
- if ($salableOnly) {
- $collection = $this->salableProcessor->process($collection);
+ $usedProducts = $this->readUsedProductsCacheData($cacheKey);
+ if ($usedProducts === null) {
+ $collection = $this->getConfiguredUsedProductCollection($product, false);
+ if ($salableOnly) {
+ $collection = $this->salableProcessor->process($collection);
+ }
+ $usedProducts = array_values($collection->getItems());
+ $this->saveUsedProductsCacheData($product, $usedProducts, $cacheKey);
}
- $usedProducts = array_values($collection->getItems());
$product->setData($dataFieldName, $usedProducts);
}
diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml
index b8f7ed67a9868..c8a278df92dc6 100644
--- a/app/code/Magento/ConfigurableProduct/etc/di.xml
+++ b/app/code/Magento/ConfigurableProduct/etc/di.xml
@@ -256,4 +256,12 @@
+
+
+ Magento\Framework\App\Cache\Type\Collection
+
+
+ Magento\Framework\Serialize\Serializer\Json
+
+
diff --git a/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml b/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml
index df96829b354c8..b2d50f54f5334 100644
--- a/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml
+++ b/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml
@@ -13,4 +13,7 @@
+
+
+