Skip to content

Commit 82ebf79

Browse files
authored
LYNX-150: Cache identity for attributesList query
1 parent b3a6b8e commit 82ebf79

File tree

5 files changed

+275
-6
lines changed

5 files changed

+275
-6
lines changed

app/code/Magento/EavGraphQl/Model/Resolver/AttributesList.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@
77

88
namespace Magento\EavGraphQl\Model\Resolver;
99

10-
use Magento\Eav\Model\AttributeRepository;
1110
use Magento\Eav\Api\Data\AttributeInterface;
12-
use Magento\Framework\GraphQl\Query\EnumLookup;
11+
use Magento\Eav\Model\AttributeRepository;
12+
use Magento\EavGraphQl\Model\Output\GetAttributeDataInterface;
1313
use Magento\Framework\Api\SearchCriteriaBuilder;
14+
use Magento\Framework\Exception\RuntimeException;
1415
use Magento\Framework\GraphQl\Config\Element\Field;
1516
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
17+
use Magento\Framework\GraphQl\Query\EnumLookup;
1618
use Magento\Framework\GraphQl\Query\ResolverInterface;
1719
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
18-
use Magento\Framework\Exception\RuntimeException;
19-
use Magento\EavGraphQl\Model\Output\GetAttributeDataInterface;
2020

2121
/**
2222
* Returns a list of attributes metadata for a given entity type.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\EavGraphQl\Model\Resolver\Cache;
9+
10+
use Magento\Framework\GraphQl\Query\Resolver\IdentityInterface;
11+
12+
/**
13+
* Cache identity provider for attributes list query results.
14+
*/
15+
class AttributesListIdentity implements IdentityInterface
16+
{
17+
public const CACHE_TAG = 'ATTRIBUTES_LIST';
18+
19+
/**
20+
* @inheritDoc
21+
*/
22+
public function getIdentities(array $resolvedData): array
23+
{
24+
if (empty($resolvedData['items'])) {
25+
return [];
26+
}
27+
28+
if (!is_array($resolvedData['items'][0])) {
29+
return [];
30+
}
31+
32+
return [sprintf(
33+
"%s_%s",
34+
self::CACHE_TAG,
35+
$resolvedData['items'][0]['entity_type']
36+
)];
37+
}
38+
}

app/code/Magento/EavGraphQl/Plugin/Eav/AttributePlugin.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace Magento\EavGraphQl\Plugin\Eav;
99

1010
use Magento\Eav\Model\Entity\Attribute;
11+
use Magento\EavGraphQl\Model\Resolver\Cache\AttributesListIdentity;
1112
use Magento\Framework\Api\AttributeInterface;
1213

1314
/**
@@ -35,7 +36,8 @@ public function afterGetIdentities(Attribute $subject, array $result): array
3536
$subject->getOrigData(AttributeInterface::ATTRIBUTE_CODE)
3637
?? $subject->getData(AttributeInterface::ATTRIBUTE_CODE)
3738
)
38-
]
39+
],
40+
[AttributesListIdentity::CACHE_TAG.'_'.strtoupper($subject->getEntityType()->getEntityTypeCode())]
3941
);
4042
}
4143
}

app/code/Magento/EavGraphQl/etc/schema.graphqls

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ type Query {
1010
@deprecated(reason: "Use `customAttributeMetadataV2` query instead.")
1111
customAttributeMetadataV2(attributes: [AttributeInput!]): AttributesMetadataOutput! @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\AttributesMetadata") @doc(description: "Retrieve EAV attributes metadata.") @cache(cacheIdentity: "Magento\\EavGraphQl\\Model\\Resolver\\Cache\\CustomAttributeMetadataV2Identity")
1212
attributesForm(formCode: String! @doc(description: "Form code.")): AttributesFormOutput! @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\AttributesForm") @doc(description: "Retrieve EAV attributes associated to a frontend form.")
13-
attributesList(entityType: AttributeEntityTypeEnum! @doc(description: "Entity type.")): AttributesMetadataOutput @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\AttributesList") @doc(description: "Returns a list of attributes metadata for a given entity type.") @cache(cacheable: false)
13+
attributesList(entityType: AttributeEntityTypeEnum! @doc(description: "Entity type.")):
14+
AttributesMetadataOutput
15+
@resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\AttributesList")
16+
@doc(description: "Returns a list of attributes metadata for a given entity type.")
17+
@cache(cacheIdentity: "Magento\\EavGraphQl\\Model\\Resolver\\Cache\\AttributesListIdentity")
1418
}
1519

1620
type CustomAttributeMetadata @doc(description: "Defines an array of custom attributes.") {
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\GraphQl\EavGraphQl;
9+
10+
use Magento\Customer\Api\AddressMetadataInterface;
11+
use Magento\Customer\Api\CustomerMetadataInterface;
12+
use Magento\Customer\Test\Fixture\CustomerAttribute;
13+
use Magento\Eav\Api\Data\AttributeInterface;
14+
use Magento\Eav\Model\AttributeFactory;
15+
use Magento\Eav\Model\AttributeRepository;
16+
use Magento\Eav\Test\Fixture\Attribute;
17+
use Magento\GraphQl\PageCache\GraphQLPageCacheAbstract;
18+
use Magento\PageCache\Model\Config;
19+
use Magento\Store\Api\Data\StoreInterface;
20+
use Magento\Store\Test\Fixture\Group as StoreGroupFixture;
21+
use Magento\Store\Test\Fixture\Store as StoreFixture;
22+
use Magento\Store\Test\Fixture\Website as WebsiteFixture;
23+
use Magento\TestFramework\Fixture\Config as ConfigFixture;
24+
use Magento\TestFramework\Fixture\DataFixture;
25+
use Magento\TestFramework\Fixture\DataFixtureStorageManager;
26+
use Magento\TestFramework\Helper\Bootstrap;
27+
28+
/**
29+
* Test caching for attributes list GraphQL query.
30+
*/
31+
class AttributesListCacheTest extends GraphQLPageCacheAbstract
32+
{
33+
private const QUERY = <<<QRY
34+
{
35+
attributesList(entityType: CUSTOMER) {
36+
items {
37+
uid
38+
code
39+
}
40+
errors {
41+
type
42+
message
43+
}
44+
}
45+
}
46+
QRY;
47+
48+
private const QUERY_ADDRESS = <<<QRY
49+
{
50+
attributesList(entityType: CUSTOMER_ADDRESS) {
51+
items {
52+
uid
53+
code
54+
}
55+
errors {
56+
type
57+
message
58+
}
59+
}
60+
}
61+
QRY;
62+
63+
/**
64+
* @var AttributeRepository
65+
*/
66+
private $eavAttributeRepo;
67+
68+
/**
69+
* @var AttributeFactory
70+
*/
71+
private $attributeFactory;
72+
73+
/**
74+
* @inheritdoc
75+
*/
76+
public function setUp(): void
77+
{
78+
$this->eavAttributeRepo = Bootstrap::getObjectManager()->get(AttributeRepository::class);
79+
/** @var AttributeFactory $attributeFactory */
80+
$this->attributeFactory = Bootstrap::getObjectManager()->create(AttributeFactory::class);
81+
parent::setUp();
82+
}
83+
84+
#[
85+
ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH),
86+
DataFixture(
87+
Attribute::class,
88+
[
89+
'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
90+
'frontend_input' => 'boolean',
91+
'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean'
92+
],
93+
'customer_attribute_0'
94+
),
95+
]
96+
public function testAttributesListCacheMissAndHit()
97+
{
98+
/** @var AttributeInterface $attribute0 */
99+
$attribute0 = DataFixtureStorageManager::getStorage()->get('customer_attribute_0');
100+
101+
$this->assertCacheMissAndReturnResponse(self::QUERY, []);
102+
$response = $this->assertCacheHitAndReturnResponse(self::QUERY, []);
103+
104+
$attribute = end($response['body']['attributesList']['items']);
105+
$this->assertEquals($attribute0->getAttributeCode(), $attribute['code']);
106+
107+
// Modify an attribute present in the response of the previous query to check cache invalidation
108+
$attribute0->setAttributeCode($attribute0->getAttributeCode() . '_modified');
109+
$this->eavAttributeRepo->save($attribute0);
110+
111+
// First query execution should result in a cache miss, while second one should be a cache hit
112+
$this->assertCacheMissAndReturnResponse(self::QUERY, []);
113+
$this->assertCacheHitAndReturnResponse(self::QUERY, []);
114+
}
115+
116+
#[
117+
ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH),
118+
DataFixture(WebsiteFixture::class, as: 'website2'),
119+
DataFixture(StoreGroupFixture::class, ['website_id' => '$website2.id$'], 'store_group2'),
120+
DataFixture(StoreFixture::class, ['store_group_id' => '$store_group2.id$'], 'store2'),
121+
DataFixture(
122+
Attribute::class,
123+
[
124+
'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
125+
'frontend_input' => 'boolean',
126+
'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean'
127+
],
128+
'customer_attribute_0'
129+
),
130+
]
131+
public function testAttributesListCacheMissAndHitDifferentStores()
132+
{
133+
/** @var StoreInterface $store2 */
134+
$store2 = DataFixtureStorageManager::getStorage()->get('store2');
135+
136+
/** @var AttributeInterface $attribute0 */
137+
$attribute0 = DataFixtureStorageManager::getStorage()->get('customer_attribute_0');
138+
139+
$response = $this->assertCacheMissAndReturnResponse(self::QUERY, []);
140+
$attribute = end($response['body']['attributesList']['items']);
141+
$this->assertEquals($attribute0->getAttributeCode(), $attribute['code']);
142+
143+
$this->assertCacheHitAndReturnResponse(self::QUERY, []);
144+
145+
// First query execution for a different store should result in a cache miss, while second one should be a hit
146+
$response = $this->assertCacheMissAndReturnResponse(self::QUERY, ['Store' => $store2->getCode()]);
147+
$attribute = end($response['body']['attributesList']['items']);
148+
$this->assertEquals($attribute0->getAttributeCode(), $attribute['code']);
149+
150+
$this->assertCacheHitAndReturnResponse(self::QUERY, ['Store' => $store2->getCode()]);
151+
}
152+
153+
#[
154+
ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH),
155+
DataFixture(
156+
Attribute::class,
157+
[
158+
'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
159+
'frontend_input' => 'boolean',
160+
'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean'
161+
],
162+
'customer_attribute_0'
163+
),
164+
DataFixture(
165+
Attribute::class,
166+
[
167+
'entity_type_id' => AddressMetadataInterface::ATTRIBUTE_SET_ID_ADDRESS,
168+
'frontend_input' => 'boolean',
169+
'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean'
170+
],
171+
'customer_address_attribute_0'
172+
),
173+
]
174+
public function testAttributeListChangeOnlyAffectsResponsesWithEntity()
175+
{
176+
/** @var AttributeInterface $customerAttribute0 */
177+
$customerAttribute0 = DataFixtureStorageManager::getStorage()->get('customer_attribute_0');
178+
179+
/** @var AttributeInterface $customerAttribute0 */
180+
$customerAddressAttribute0 = DataFixtureStorageManager::getStorage()->get('customer_address_attribute_0');
181+
182+
$this->assertCacheMissAndReturnResponse(self::QUERY, []);
183+
$response = $this->assertCacheHitAndReturnResponse(self::QUERY, []);
184+
185+
$attribute = end($response['body']['attributesList']['items']);
186+
$this->assertEquals($customerAttribute0->getAttributeCode(), $attribute['code']);
187+
188+
$this->assertCacheMissAndReturnResponse(self::QUERY_ADDRESS, []);
189+
$this->assertCacheHitAndReturnResponse(self::QUERY_ADDRESS, []);
190+
191+
$customerAttribute0->setAttributeCode($customerAttribute0->getAttributeCode() . '_modified');
192+
$this->eavAttributeRepo->save($customerAttribute0);
193+
194+
$response = $this->assertCacheHitAndReturnResponse(self::QUERY_ADDRESS, []);
195+
$attribute = end($response['body']['attributesList']['items']);
196+
$this->assertEquals($customerAddressAttribute0->getAttributeCode(), $attribute['code']);
197+
}
198+
199+
#[
200+
ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH),
201+
]
202+
public function testAttributesListCacheMissAndHitNewAttribute()
203+
{
204+
$this->assertCacheMissAndReturnResponse(self::QUERY, []);
205+
$this->assertCacheHitAndReturnResponse(self::QUERY, []);
206+
207+
$newAttributeCreate = Bootstrap::getObjectManager()->get(CustomerAttribute::class);
208+
/** @var AttributeInterface $newAttribute */
209+
$newAttribute = $newAttributeCreate->apply([
210+
'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
211+
'frontend_input' => 'boolean',
212+
'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean'
213+
]);
214+
215+
// First query execution should result in a cache miss, while second one should be a cache hit
216+
$this->assertCacheMissAndReturnResponse(self::QUERY, []);
217+
$this->assertCacheHitAndReturnResponse(self::QUERY, []);
218+
219+
$this->eavAttributeRepo->delete($newAttribute);
220+
221+
// Check that the same mentioned above applies if we delete an attribute present in the response
222+
$this->assertCacheMissAndReturnResponse(self::QUERY, []);
223+
$this->assertCacheHitAndReturnResponse(self::QUERY, []);
224+
}
225+
}

0 commit comments

Comments
 (0)