From 055f074baab24e54e148b865ffe5361c4dfb862b Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov Date: Mon, 8 May 2017 15:53:19 +0300 Subject: [PATCH 1/3] MAGETWO-66825: [Develop][Performance] Caching of attribute options for Layered Navigation --- .../Attribute/Frontend/AbstractFrontend.php | 75 +++++++++++-- .../Frontend/DefaultFrontendTest.php | 104 +++++++++++++++++- .../Frontend/DefaultFrontendTest.php | 91 +++++++++++++++ 3 files changed, 258 insertions(+), 12 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Eav/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php b/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php index ab0760e780a47..ed345248ebe07 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php @@ -11,11 +11,43 @@ */ namespace Magento\Eav\Model\Entity\Attribute\Frontend; -/** - * @api - */ +use Magento\Framework\App\CacheInterface; +use Magento\Framework\Serialize\Serializer\Json as Serializer; +use Magento\Store\Api\StoreResolverInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Eav\Model\Cache\Type as CacheType; +use Magento\Eav\Model\Entity\Attribute; +use Magento\Eav\Model\Entity\Attribute\Source\BooleanFactory; + abstract class AbstractFrontend implements \Magento\Eav\Model\Entity\Attribute\Frontend\FrontendInterface { + /** + * Default cache tags values + * will be used if no values in the constructor provided + * @var array + */ + private static $defaultCacheTags = [CacheType::CACHE_TAG, Attribute::CACHE_TAG]; + + /** + * @var CacheInterface + */ + private $cache; + + /** + * @var StoreResolverInterface + */ + private $storeResolver; + + /** + * @var Serializer + */ + private $serializer; + + /** + * @var array + */ + private $cacheTags; + /** * Reference to the attribute instance * @@ -24,17 +56,30 @@ abstract class AbstractFrontend implements \Magento\Eav\Model\Entity\Attribute\F protected $_attribute; /** - * @var \Magento\Eav\Model\Entity\Attribute\Source\BooleanFactory + * @var BooleanFactory */ protected $_attrBooleanFactory; /** - * @param \Magento\Eav\Model\Entity\Attribute\Source\BooleanFactory $attrBooleanFactory + * @param BooleanFactory $attrBooleanFactory + * @param CacheInterface $cache + * @param StoreResolverInterface $storeResolver + * @param array $cacheTags + * @param Serializer $serializer * @codeCoverageIgnore */ - public function __construct(\Magento\Eav\Model\Entity\Attribute\Source\BooleanFactory $attrBooleanFactory) - { + public function __construct( + BooleanFactory $attrBooleanFactory, + CacheInterface $cache = null, + StoreResolverInterface $storeResolver = null, + array $cacheTags = null, + Serializer $serializer = null + ) { $this->_attrBooleanFactory = $attrBooleanFactory; + $this->cache = $cache ?: ObjectManager::getInstance()->get(CacheInterface::class); + $this->storeResolver = $storeResolver ?: ObjectManager::getInstance()->get(StoreResolverInterface::class); + $this->cacheTags = $cacheTags ?: self::$defaultCacheTags; + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Serializer::class); } /** @@ -249,7 +294,21 @@ public function getConfigField($fieldName) */ public function getSelectOptions() { - return $this->getAttribute()->getSource()->getAllOptions(); + $cacheKey = 'attribute-navigation-option-' . + $this->getAttribute()->getAttributeCode() . '-' . + $this->storeResolver->getCurrentStoreId(); + $optionString = $this->cache->load($cacheKey); + if (false === $optionString) { + $options = $this->getAttribute()->getSource()->getAllOptions(); + $this->cache->save( + $this->serializer->serialize($options), + $cacheKey, + $this->cacheTags + ); + } else { + $options = $this->serializer->unserialize($optionString); + } + return $options; } /** diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php index e8f67879eba83..5dd00fb3bc4fb 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php @@ -6,6 +6,12 @@ namespace Magento\Eav\Test\Unit\Model\Entity\Attribute\Frontend; use Magento\Eav\Model\Entity\Attribute\Frontend\DefaultFrontend; +use Magento\Eav\Model\Entity\Attribute\Source\BooleanFactory; +use Magento\Framework\Serialize\Serializer\Json as Serializer; +use Magento\Store\Api\StoreResolverInterface; +use Magento\Framework\App\CacheInterface; +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource; class DefaultFrontendTest extends \PHPUnit_Framework_TestCase { @@ -15,18 +21,73 @@ class DefaultFrontendTest extends \PHPUnit_Framework_TestCase protected $model; /** - * @var \Magento\Eav\Model\Entity\Attribute\Source\BooleanFactory|\PHPUnit_Framework_MockObject_MockObject + * @var BooleanFactory|\PHPUnit_Framework_MockObject_MockObject */ protected $booleanFactory; + /** + * @var Serializer|\PHPUnit_Framework_MockObject_MockObject + */ + private $serializerMock; + + /** + * @var StoreResolverInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeResolverMock; + + /** + * @var CacheInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $cacheMock; + + /** + * @var AbstractAttribute|\PHPUnit_Framework_MockObject_MockObject + */ + private $attributeMock; + + /** + * @var array + */ + private $cacheTags; + + /** + * @var AbstractSource|\PHPUnit_Framework_MockObject_MockObject + */ + private $sourceMock; + protected function setUp() { - $this->booleanFactory = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\Source\BooleanFactory::class) + $this->cacheTags = ['tag1', 'tag2']; + + $this->booleanFactory = $this->getMockBuilder(BooleanFactory::class) ->disableOriginalConstructor() ->getMock(); + $this->serializerMock = $this->getMockBuilder(Serializer::class) + ->getMock(); + $this->storeResolverMock = $this->getMockBuilder(StoreResolverInterface::class) + ->getMockForAbstractClass(); + $this->cacheMock = $this->getMockBuilder(CacheInterface::class) + ->getMockForAbstractClass(); + $this->attributeMock = $this->getMockBuilder(AbstractAttribute::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributeCode', 'getSource']) + ->getMockForAbstractClass(); + $this->sourceMock = $this->getMockBuilder(AbstractSource::class) + ->disableOriginalConstructor() + ->setMethods(['getAllOptions']) + ->getMockForAbstractClass(); - $this->model = new DefaultFrontend( - $this->booleanFactory + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->model = $objectManager->getObject( + DefaultFrontend::class, + [ + '_attrBooleanFactory' => $this->booleanFactory, + 'cache' => $this->cacheMock, + 'storeResolver' => $this->storeResolverMock, + 'serializer' => $this->serializerMock, + '_attribute' => $this->attributeMock, + 'cacheTags' => $this->cacheTags + ] ); } @@ -118,4 +179,39 @@ public function testGetClassLength() $this->assertContains('maximum-length-2', $result); $this->assertContains('validate-length', $result); } + + public function testGetSelectOptions() + { + $storeId = 1; + $attributeCode = 'attr1'; + $cacheKey = 'attribute-navigation-option-' . $attributeCode . '-' . $storeId; + $options = ['option1', 'option2']; + $serializedOptions = "{['option1', 'option2']}"; + + $this->storeResolverMock->expects($this->once()) + ->method('getCurrentStoreId') + ->willReturn($storeId); + $this->attributeMock->expects($this->once()) + ->method('getAttributeCode') + ->willReturn($attributeCode); + $this->cacheMock->expects($this->once()) + ->method('load') + ->with($cacheKey) + ->willReturn(false); + $this->attributeMock->expects($this->once()) + ->method('getSource') + ->willReturn($this->sourceMock); + $this->sourceMock->expects($this->once()) + ->method('getAllOptions') + ->willReturn($options); + $this->serializerMock->expects($this->once()) + ->method('serialize') + ->with($options) + ->willReturn($serializedOptions); + $this->cacheMock->expects($this->once()) + ->method('save') + ->with($serializedOptions, $cacheKey, $this->cacheTags); + + $this->assertSame($options, $this->model->getSelectOptions()); + } } diff --git a/dev/tests/integration/testsuite/Magento/Eav/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php b/dev/tests/integration/testsuite/Magento/Eav/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php new file mode 100644 index 0000000000000..7a9a0f781b28f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Eav/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php @@ -0,0 +1,91 @@ +objectManager = Bootstrap::getObjectManager(); + + $this->defaultFrontend = $this->objectManager->get(DefaultFrontend::class); + $this->cache = $this->objectManager->get(CacheInterface::class); + $this->storeResolver = $this->objectManager->get(StoreResolverInterface::class); + $this->serializer = $this->objectManager->get(Serializer::class); + $this->attribute = $this->objectManager->get(Attribute::class); + + $this->attribute->setAttributeCode('store_id'); + $this->options = $this->attribute->getSource()->getAllOptions(); + $this->defaultFrontend->setAttribute($this->attribute); + } + + public function testGetSelectOptions() + { + $this->assertSame($this->options, $this->defaultFrontend->getSelectOptions()); + $this->assertSame( + $this->serializer->serialize($this->options), + $this->cache->load($this->getCacheKey()) + ); + } + + /** + * Cache key generation + * @return string + */ + private function getCacheKey() + { + return 'attribute-navigation-option-' . + $this->defaultFrontend->getAttribute()->getAttributeCode() . '-' . + $this->storeResolver->getCurrentStoreId(); + } +} \ No newline at end of file From 0c5c1a26d1e82d63ef54df3a74f5b402f1b16933 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov Date: Wed, 10 May 2017 11:52:51 +0300 Subject: [PATCH 2/3] MAGETWO-66825: [Develop][Performance] Caching of attribute options for Layered Navigation --- .../Eav/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Eav/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php b/dev/tests/integration/testsuite/Magento/Eav/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php index 7a9a0f781b28f..3018c382f8b03 100644 --- a/dev/tests/integration/testsuite/Magento/Eav/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php +++ b/dev/tests/integration/testsuite/Magento/Eav/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php @@ -88,4 +88,4 @@ private function getCacheKey() $this->defaultFrontend->getAttribute()->getAttributeCode() . '-' . $this->storeResolver->getCurrentStoreId(); } -} \ No newline at end of file +} From 49706cae9ec14a1be30f766bfb61bf043e788ccf Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov Date: Wed, 10 May 2017 14:00:15 +0300 Subject: [PATCH 3/3] MAGETWO-66825: [Develop][Performance] Caching of attribute options for Layered Navigation --- .../Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php b/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php index ed345248ebe07..32b72023340b5 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php @@ -19,6 +19,9 @@ use Magento\Eav\Model\Entity\Attribute; use Magento\Eav\Model\Entity\Attribute\Source\BooleanFactory; +/** + * @api + */ abstract class AbstractFrontend implements \Magento\Eav\Model\Entity\Attribute\Frontend\FrontendInterface { /**