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 @@ + + +