Skip to content

Commit 938313e

Browse files
author
Volodymyr Klymenko
authored
Merge pull request #1114 from magento-tango/MAGETWO-66825-1
[Tango] MAGETWO-66825: Caching of attribute options for Layered Navigation
2 parents bdb0464 + 512ee7c commit 938313e

File tree

3 files changed

+258
-9
lines changed

3 files changed

+258
-9
lines changed

app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,46 @@
1111
*/
1212
namespace Magento\Eav\Model\Entity\Attribute\Frontend;
1313

14+
use Magento\Framework\App\CacheInterface;
15+
use Magento\Framework\Serialize\Serializer\Json as Serializer;
16+
use Magento\Store\Api\StoreResolverInterface;
17+
use Magento\Framework\App\ObjectManager;
18+
use Magento\Eav\Model\Cache\Type as CacheType;
19+
use Magento\Eav\Model\Entity\Attribute;
20+
use Magento\Eav\Model\Entity\Attribute\Source\BooleanFactory;
21+
1422
/**
1523
* @api
1624
*/
1725
abstract class AbstractFrontend implements \Magento\Eav\Model\Entity\Attribute\Frontend\FrontendInterface
1826
{
27+
/**
28+
* Default cache tags values
29+
* will be used if no values in the constructor provided
30+
* @var array
31+
*/
32+
private static $defaultCacheTags = [CacheType::CACHE_TAG, Attribute::CACHE_TAG];
33+
34+
/**
35+
* @var CacheInterface
36+
*/
37+
private $cache;
38+
39+
/**
40+
* @var StoreResolverInterface
41+
*/
42+
private $storeResolver;
43+
44+
/**
45+
* @var Serializer
46+
*/
47+
private $serializer;
48+
49+
/**
50+
* @var array
51+
*/
52+
private $cacheTags;
53+
1954
/**
2055
* Reference to the attribute instance
2156
*
@@ -24,17 +59,30 @@ abstract class AbstractFrontend implements \Magento\Eav\Model\Entity\Attribute\F
2459
protected $_attribute;
2560

2661
/**
27-
* @var \Magento\Eav\Model\Entity\Attribute\Source\BooleanFactory
62+
* @var BooleanFactory
2863
*/
2964
protected $_attrBooleanFactory;
3065

3166
/**
32-
* @param \Magento\Eav\Model\Entity\Attribute\Source\BooleanFactory $attrBooleanFactory
67+
* @param BooleanFactory $attrBooleanFactory
68+
* @param CacheInterface $cache
69+
* @param StoreResolverInterface $storeResolver
70+
* @param array $cacheTags
71+
* @param Serializer $serializer
3372
* @codeCoverageIgnore
3473
*/
35-
public function __construct(\Magento\Eav\Model\Entity\Attribute\Source\BooleanFactory $attrBooleanFactory)
36-
{
74+
public function __construct(
75+
BooleanFactory $attrBooleanFactory,
76+
CacheInterface $cache = null,
77+
StoreResolverInterface $storeResolver = null,
78+
array $cacheTags = null,
79+
Serializer $serializer = null
80+
) {
3781
$this->_attrBooleanFactory = $attrBooleanFactory;
82+
$this->cache = $cache ?: ObjectManager::getInstance()->get(CacheInterface::class);
83+
$this->storeResolver = $storeResolver ?: ObjectManager::getInstance()->get(StoreResolverInterface::class);
84+
$this->cacheTags = $cacheTags ?: self::$defaultCacheTags;
85+
$this->serializer = $serializer ?: ObjectManager::getInstance()->get(Serializer::class);
3886
}
3987

4088
/**
@@ -249,7 +297,21 @@ public function getConfigField($fieldName)
249297
*/
250298
public function getSelectOptions()
251299
{
252-
return $this->getAttribute()->getSource()->getAllOptions();
300+
$cacheKey = 'attribute-navigation-option-' .
301+
$this->getAttribute()->getAttributeCode() . '-' .
302+
$this->storeResolver->getCurrentStoreId();
303+
$optionString = $this->cache->load($cacheKey);
304+
if (false === $optionString) {
305+
$options = $this->getAttribute()->getSource()->getAllOptions();
306+
$this->cache->save(
307+
$this->serializer->serialize($options),
308+
$cacheKey,
309+
$this->cacheTags
310+
);
311+
} else {
312+
$options = $this->serializer->unserialize($optionString);
313+
}
314+
return $options;
253315
}
254316

255317
/**

app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
namespace Magento\Eav\Test\Unit\Model\Entity\Attribute\Frontend;
77

88
use Magento\Eav\Model\Entity\Attribute\Frontend\DefaultFrontend;
9+
use Magento\Eav\Model\Entity\Attribute\Source\BooleanFactory;
10+
use Magento\Framework\Serialize\Serializer\Json as Serializer;
11+
use Magento\Store\Api\StoreResolverInterface;
12+
use Magento\Framework\App\CacheInterface;
13+
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
14+
use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource;
915

1016
class DefaultFrontendTest extends \PHPUnit_Framework_TestCase
1117
{
@@ -15,18 +21,73 @@ class DefaultFrontendTest extends \PHPUnit_Framework_TestCase
1521
protected $model;
1622

1723
/**
18-
* @var \Magento\Eav\Model\Entity\Attribute\Source\BooleanFactory|\PHPUnit_Framework_MockObject_MockObject
24+
* @var BooleanFactory|\PHPUnit_Framework_MockObject_MockObject
1925
*/
2026
protected $booleanFactory;
2127

28+
/**
29+
* @var Serializer|\PHPUnit_Framework_MockObject_MockObject
30+
*/
31+
private $serializerMock;
32+
33+
/**
34+
* @var StoreResolverInterface|\PHPUnit_Framework_MockObject_MockObject
35+
*/
36+
private $storeResolverMock;
37+
38+
/**
39+
* @var CacheInterface|\PHPUnit_Framework_MockObject_MockObject
40+
*/
41+
private $cacheMock;
42+
43+
/**
44+
* @var AbstractAttribute|\PHPUnit_Framework_MockObject_MockObject
45+
*/
46+
private $attributeMock;
47+
48+
/**
49+
* @var array
50+
*/
51+
private $cacheTags;
52+
53+
/**
54+
* @var AbstractSource|\PHPUnit_Framework_MockObject_MockObject
55+
*/
56+
private $sourceMock;
57+
2258
protected function setUp()
2359
{
24-
$this->booleanFactory = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\Source\BooleanFactory::class)
60+
$this->cacheTags = ['tag1', 'tag2'];
61+
62+
$this->booleanFactory = $this->getMockBuilder(BooleanFactory::class)
2563
->disableOriginalConstructor()
2664
->getMock();
65+
$this->serializerMock = $this->getMockBuilder(Serializer::class)
66+
->getMock();
67+
$this->storeResolverMock = $this->getMockBuilder(StoreResolverInterface::class)
68+
->getMockForAbstractClass();
69+
$this->cacheMock = $this->getMockBuilder(CacheInterface::class)
70+
->getMockForAbstractClass();
71+
$this->attributeMock = $this->getMockBuilder(AbstractAttribute::class)
72+
->disableOriginalConstructor()
73+
->setMethods(['getAttributeCode', 'getSource'])
74+
->getMockForAbstractClass();
75+
$this->sourceMock = $this->getMockBuilder(AbstractSource::class)
76+
->disableOriginalConstructor()
77+
->setMethods(['getAllOptions'])
78+
->getMockForAbstractClass();
2779

28-
$this->model = new DefaultFrontend(
29-
$this->booleanFactory
80+
$objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
81+
$this->model = $objectManager->getObject(
82+
DefaultFrontend::class,
83+
[
84+
'_attrBooleanFactory' => $this->booleanFactory,
85+
'cache' => $this->cacheMock,
86+
'storeResolver' => $this->storeResolverMock,
87+
'serializer' => $this->serializerMock,
88+
'_attribute' => $this->attributeMock,
89+
'cacheTags' => $this->cacheTags
90+
]
3091
);
3192
}
3293

@@ -118,4 +179,39 @@ public function testGetClassLength()
118179
$this->assertContains('maximum-length-2', $result);
119180
$this->assertContains('validate-length', $result);
120181
}
182+
183+
public function testGetSelectOptions()
184+
{
185+
$storeId = 1;
186+
$attributeCode = 'attr1';
187+
$cacheKey = 'attribute-navigation-option-' . $attributeCode . '-' . $storeId;
188+
$options = ['option1', 'option2'];
189+
$serializedOptions = "{['option1', 'option2']}";
190+
191+
$this->storeResolverMock->expects($this->once())
192+
->method('getCurrentStoreId')
193+
->willReturn($storeId);
194+
$this->attributeMock->expects($this->once())
195+
->method('getAttributeCode')
196+
->willReturn($attributeCode);
197+
$this->cacheMock->expects($this->once())
198+
->method('load')
199+
->with($cacheKey)
200+
->willReturn(false);
201+
$this->attributeMock->expects($this->once())
202+
->method('getSource')
203+
->willReturn($this->sourceMock);
204+
$this->sourceMock->expects($this->once())
205+
->method('getAllOptions')
206+
->willReturn($options);
207+
$this->serializerMock->expects($this->once())
208+
->method('serialize')
209+
->with($options)
210+
->willReturn($serializedOptions);
211+
$this->cacheMock->expects($this->once())
212+
->method('save')
213+
->with($serializedOptions, $cacheKey, $this->cacheTags);
214+
215+
$this->assertSame($options, $this->model->getSelectOptions());
216+
}
121217
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Eav\Model\Entity\Attribute\Frontend;
7+
8+
use Magento\TestFramework\Helper\Bootstrap;
9+
use Magento\TestFramework\Helper\CacheCleaner;
10+
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
11+
use Magento\Framework\App\CacheInterface;
12+
use Magento\Store\Api\StoreResolverInterface;
13+
use Magento\Framework\Serialize\Serializer\Json as Serializer;
14+
use Magento\Eav\Model\Entity\Attribute;
15+
16+
/**
17+
* @magentoAppIsolation enabled
18+
*/
19+
class DefaultFrontendTest extends \PHPUnit_Framework_TestCase
20+
{
21+
/**
22+
* @var DefaultFrontend
23+
*/
24+
private $defaultFrontend;
25+
26+
/**
27+
* @var \Magento\Framework\ObjectManagerInterface
28+
*/
29+
private $objectManager;
30+
31+
/**
32+
* @var AbstractAttribute
33+
*/
34+
private $attribute;
35+
36+
/**
37+
* @var array
38+
*/
39+
private $options;
40+
41+
/**
42+
* @var CacheInterface
43+
*/
44+
private $cache;
45+
46+
/**
47+
* @var StoreResolverInterface
48+
*/
49+
private $storeResolver;
50+
51+
/**
52+
* @var Serializer
53+
*/
54+
private $serializer;
55+
56+
protected function setUp()
57+
{
58+
CacheCleaner::cleanAll();
59+
$this->objectManager = Bootstrap::getObjectManager();
60+
61+
$this->defaultFrontend = $this->objectManager->get(DefaultFrontend::class);
62+
$this->cache = $this->objectManager->get(CacheInterface::class);
63+
$this->storeResolver = $this->objectManager->get(StoreResolverInterface::class);
64+
$this->serializer = $this->objectManager->get(Serializer::class);
65+
$this->attribute = $this->objectManager->get(Attribute::class);
66+
67+
$this->attribute->setAttributeCode('store_id');
68+
$this->options = $this->attribute->getSource()->getAllOptions();
69+
$this->defaultFrontend->setAttribute($this->attribute);
70+
}
71+
72+
public function testGetSelectOptions()
73+
{
74+
$this->assertSame($this->options, $this->defaultFrontend->getSelectOptions());
75+
$this->assertSame(
76+
$this->serializer->serialize($this->options),
77+
$this->cache->load($this->getCacheKey())
78+
);
79+
}
80+
81+
/**
82+
* Cache key generation
83+
* @return string
84+
*/
85+
private function getCacheKey()
86+
{
87+
return 'attribute-navigation-option-' .
88+
$this->defaultFrontend->getAttribute()->getAttributeCode() . '-' .
89+
$this->storeResolver->getCurrentStoreId();
90+
}
91+
}

0 commit comments

Comments
 (0)