Skip to content

Commit

Permalink
MC-19689: Simple product disappearing in the configurable grid after …
Browse files Browse the repository at this point in the history
…qty set to 0
  • Loading branch information
dhorytskyi committed Aug 30, 2019
1 parent c243fc7 commit 2487db1
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\ConfigurableProduct\Model\Plugin\Frontend;

use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\Data\ProductInterfaceFactory;
use Magento\Catalog\Model\Category;
use Magento\Catalog\Model\Product;
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
use Magento\Customer\Model\Session;
use Magento\Framework\Cache\FrontendInterface;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Serialize\SerializerInterface;

/**
* Cache of used products for configurable product
*/
class UsedProductsCache
{
/**
* @var MetadataPool
*/
private $metadataPool;

/**
* @var FrontendInterface
*/
private $cache;

/**
* @var SerializerInterface
*/
private $serializer;

/**
* @var ProductInterfaceFactory
*/
private $productFactory;

/**
* @var Session
*/
private $customerSession;

/**
* @param MetadataPool $metadataPool
* @param FrontendInterface $cache
* @param SerializerInterface $serializer
* @param ProductInterfaceFactory $productFactory
* @param Session $customerSession
*/
public function __construct(
MetadataPool $metadataPool,
FrontendInterface $cache,
SerializerInterface $serializer,
ProductInterfaceFactory $productFactory,
Session $customerSession
) {
$this->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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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);
}

Expand Down
8 changes: 8 additions & 0 deletions app/code/Magento/ConfigurableProduct/etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -256,4 +256,12 @@
</argument>
</arguments>
</type>
<type name="Magento\ConfigurableProduct\Model\Plugin\Frontend\UsedProductsCache">
<arguments>
<argument name="cache" xsi:type="object">Magento\Framework\App\Cache\Type\Collection</argument>
</arguments>
<arguments>
<argument name="serializer" xsi:type="object">Magento\Framework\Serialize\Serializer\Json</argument>
</arguments>
</type>
</config>
3 changes: 3 additions & 0 deletions app/code/Magento/ConfigurableProduct/etc/frontend/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@
<type name="Magento\Catalog\Model\Product">
<plugin name="product_identities_extender" type="Magento\ConfigurableProduct\Model\Plugin\Frontend\ProductIdentitiesExtender" />
</type>
<type name="Magento\ConfigurableProduct\Model\Product\Type\Configurable">
<plugin name="used_products_cache" type="Magento\ConfigurableProduct\Model\Plugin\Frontend\UsedProductsCache" />
</type>
</config>

0 comments on commit 2487db1

Please sign in to comment.