diff --git a/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php b/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php
index 764658b3ac00a..f7ee3e6c27914 100644
--- a/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php
+++ b/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php
@@ -6,11 +6,23 @@
namespace Magento\SalesRule\Model\Plugin;
+use Magento\Framework\App\CacheInterface;
+use Magento\Framework\Serialize\SerializerInterface;
use Magento\Quote\Model\Quote\Config;
use Magento\SalesRule\Model\ResourceModel\Rule as RuleResource;
class QuoteConfigProductAttributes
{
+ /**
+ * Cache key for active salesrule attributes
+ */
+ private const CACHE_KEY = 'salesrule_active_product_attributes';
+
+ /**
+ * Cache tag for salesrule attributes
+ */
+ private const CACHE_TAG = 'salesrule';
+
/**
* @var RuleResource
*/
@@ -21,17 +33,36 @@ class QuoteConfigProductAttributes
*/
private $activeAttributeCodes;
+ /**
+ * @var CacheInterface
+ */
+ private $cache;
+
+ /**
+ * @var SerializerInterface
+ */
+ private $serializer;
+
/**
* @param RuleResource $ruleResource
+ * @param CacheInterface $cache
+ * @param SerializerInterface $serializer
*/
- public function __construct(RuleResource $ruleResource)
- {
+ public function __construct(
+ RuleResource $ruleResource,
+ CacheInterface $cache,
+ SerializerInterface $serializer
+ ) {
$this->ruleResource = $ruleResource;
+ $this->cache = $cache;
+ $this->serializer = $serializer;
}
/**
* Append sales rule product attribute keys to select by quote item collection
*
+ * Uses cache to avoid database queries on every request.
+ *
* @param Config $subject
* @param array $attributeKeys
*
@@ -41,7 +72,21 @@ public function __construct(RuleResource $ruleResource)
public function afterGetProductAttributes(Config $subject, array $attributeKeys): array
{
if ($this->activeAttributeCodes === null) {
- $this->activeAttributeCodes = array_column($this->ruleResource->getActiveAttributes(), 'attribute_code');
+ $cachedData = $this->cache->load(self::CACHE_KEY);
+
+ if ($cachedData !== false) {
+ $this->activeAttributeCodes = $this->serializer->unserialize($cachedData);
+ } else {
+ $this->activeAttributeCodes = array_column(
+ $this->ruleResource->getActiveAttributes(),
+ 'attribute_code'
+ );
+ $this->cache->save(
+ $this->serializer->serialize($this->activeAttributeCodes),
+ self::CACHE_KEY,
+ [self::CACHE_TAG]
+ );
+ }
}
return array_merge($attributeKeys, $this->activeAttributeCodes);
diff --git a/app/code/Magento/SalesRule/Observer/ClearProductAttributesCacheObserver.php b/app/code/Magento/SalesRule/Observer/ClearProductAttributesCacheObserver.php
new file mode 100644
index 0000000000000..68e14e2bef4c9
--- /dev/null
+++ b/app/code/Magento/SalesRule/Observer/ClearProductAttributesCacheObserver.php
@@ -0,0 +1,47 @@
+cache = $cache;
+ }
+
+ /**
+ * Clear salesrule product attributes cache
+ *
+ * @param Observer $observer
+ * @return void
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function execute(Observer $observer): void
+ {
+ $this->cache->remove(self::CACHE_KEY);
+ }
+}
diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/QuoteConfigProductAttributesTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/QuoteConfigProductAttributesTest.php
index a06584cec2273..c9b2f211c5463 100644
--- a/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/QuoteConfigProductAttributesTest.php
+++ b/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/QuoteConfigProductAttributesTest.php
@@ -7,6 +7,8 @@
namespace Magento\SalesRule\Test\Unit\Model\Plugin;
+use Magento\Framework\App\CacheInterface;
+use Magento\Framework\Serialize\SerializerInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Quote\Model\Quote\Config;
use Magento\SalesRule\Model\Plugin\QuoteConfigProductAttributes;
@@ -17,7 +19,7 @@
class QuoteConfigProductAttributesTest extends TestCase
{
/**
- * @var QuoteConfigProductAttributes|MockObject
+ * @var QuoteConfigProductAttributes
*/
protected $plugin;
@@ -26,24 +28,67 @@ class QuoteConfigProductAttributesTest extends TestCase
*/
protected $ruleResource;
+ /**
+ * @var CacheInterface|MockObject
+ */
+ protected $cache;
+
+ /**
+ * @var SerializerInterface|MockObject
+ */
+ protected $serializer;
+
protected function setUp(): void
{
$objectManager = new ObjectManager($this);
$this->ruleResource = $this->createMock(Rule::class);
+ $this->cache = $this->createMock(CacheInterface::class);
+ $this->serializer = $this->createMock(SerializerInterface::class);
$this->plugin = $objectManager->getObject(
QuoteConfigProductAttributes::class,
[
- 'ruleResource' => $this->ruleResource
+ 'ruleResource' => $this->ruleResource,
+ 'cache' => $this->cache,
+ 'serializer' => $this->serializer
]
);
}
- public function testAfterGetProductAttributes()
+ public function testAfterGetProductAttributesWithCache()
+ {
+ $subject = $this->createMock(Config::class);
+ $attributeCode = 'code of the attribute';
+ $expected = [0 => $attributeCode];
+ $serializedData = '["' . $attributeCode . '"]';
+
+ $this->cache->expects($this->once())
+ ->method('load')
+ ->with('salesrule_active_product_attributes')
+ ->willReturn($serializedData);
+
+ $this->serializer->expects($this->once())
+ ->method('unserialize')
+ ->with($serializedData)
+ ->willReturn([$attributeCode]);
+
+ $this->ruleResource->expects($this->never())
+ ->method('getActiveAttributes');
+
+ $this->assertEquals($expected, $this->plugin->afterGetProductAttributes($subject, []));
+ }
+
+ public function testAfterGetProductAttributesWithoutCache()
{
$subject = $this->createMock(Config::class);
$attributeCode = 'code of the attribute';
$expected = [0 => $attributeCode];
+ $serializedData = '["' . $attributeCode . '"]';
+
+ $this->cache->expects($this->once())
+ ->method('load')
+ ->with('salesrule_active_product_attributes')
+ ->willReturn(false);
$this->ruleResource->expects($this->once())
->method('getActiveAttributes')
@@ -53,6 +98,15 @@ public function testAfterGetProductAttributes()
]
);
+ $this->serializer->expects($this->once())
+ ->method('serialize')
+ ->with([$attributeCode])
+ ->willReturn($serializedData);
+
+ $this->cache->expects($this->once())
+ ->method('save')
+ ->with($serializedData, 'salesrule_active_product_attributes', ['salesrule']);
+
$this->assertEquals($expected, $this->plugin->afterGetProductAttributes($subject, []));
}
}
diff --git a/app/code/Magento/SalesRule/Test/Unit/Observer/ClearProductAttributesCacheObserverTest.php b/app/code/Magento/SalesRule/Test/Unit/Observer/ClearProductAttributesCacheObserverTest.php
new file mode 100644
index 0000000000000..904b642b0731b
--- /dev/null
+++ b/app/code/Magento/SalesRule/Test/Unit/Observer/ClearProductAttributesCacheObserverTest.php
@@ -0,0 +1,44 @@
+cache = $this->createMock(CacheInterface::class);
+ $this->observer = new ClearProductAttributesCacheObserver($this->cache);
+ }
+
+ public function testExecuteClearsCache()
+ {
+ $event = $this->createMock(Observer::class);
+
+ $this->cache->expects($this->once())
+ ->method('remove')
+ ->with('salesrule_active_product_attributes');
+
+ $this->observer->execute($event);
+ }
+}
diff --git a/app/code/Magento/SalesRule/etc/events.xml b/app/code/Magento/SalesRule/etc/events.xml
index f430ef2ee8758..20e448a5ef3a1 100644
--- a/app/code/Magento/SalesRule/etc/events.xml
+++ b/app/code/Magento/SalesRule/etc/events.xml
@@ -32,9 +32,11 @@
+
+