Skip to content

Commit 5fa608c

Browse files
authored
Merge branch '2.4-develop' into fix-for-issue-#35906
2 parents 7b13a9c + fd5b8d0 commit 5fa608c

File tree

16 files changed

+3476
-41
lines changed

16 files changed

+3476
-41
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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\Eav\Model\Entity\Attribute as EavAttribute;
11+
use Magento\Framework\GraphQl\Query\Resolver\IdentityInterface;
12+
13+
/**
14+
* Cache identity provider for custom attribute metadata query results.
15+
*/
16+
class CustomAttributeMetadataIdentity implements IdentityInterface
17+
{
18+
/**
19+
* @inheritDoc
20+
*/
21+
public function getIdentities(array $resolvedData): array
22+
{
23+
$identities = [];
24+
if (isset($resolvedData['items']) && !empty($resolvedData['items'])) {
25+
foreach ($resolvedData['items'] as $item) {
26+
if (is_array($item)) {
27+
$identities[] = sprintf(
28+
"%s_%s_%s",
29+
EavAttribute::CACHE_TAG,
30+
$item['entity_type'],
31+
$item['attribute_code']
32+
);
33+
}
34+
}
35+
} else {
36+
return [];
37+
}
38+
return $identities;
39+
}
40+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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\Plugin\Eav;
9+
10+
use Magento\Eav\Model\Entity\Attribute;
11+
use Magento\Framework\Api\AttributeInterface;
12+
13+
/**
14+
* EAV plugin runs page cache clean and provides proper EAV identities.
15+
*/
16+
class AttributePlugin
17+
{
18+
/**
19+
* Clean cache by relevant tags after entity save.
20+
*
21+
* @param Attribute $subject
22+
* @param array $result
23+
*
24+
* @return string[]
25+
*/
26+
public function afterGetIdentities(Attribute $subject, array $result): array
27+
{
28+
return array_merge(
29+
$result,
30+
[
31+
sprintf(
32+
"%s_%s_%s",
33+
Attribute::CACHE_TAG,
34+
$subject->getEntityType()->getEntityTypeCode(),
35+
$subject->getOrigData(AttributeInterface::ATTRIBUTE_CODE)
36+
?? $subject->getData(AttributeInterface::ATTRIBUTE_CODE)
37+
)
38+
]
39+
);
40+
}
41+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
9+
<type name="Magento\Eav\Model\Entity\Attribute">
10+
<plugin name="entityAttributeChangePlugin" type="Magento\EavGraphQl\Plugin\Eav\AttributePlugin" />
11+
</type>
12+
</config>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# See COPYING.txt for license details.
33

44
type Query {
5-
customAttributeMetadata(attributes: [AttributeInput!]! @doc(description: "An input object that specifies the attribute code and entity type to search.")): CustomAttributeMetadata @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\CustomAttributeMetadata") @doc(description: "Return the attribute type, given an attribute code and entity type.") @cache(cacheable: false)
5+
customAttributeMetadata(attributes: [AttributeInput!]! @doc(description: "An input object that specifies the attribute code and entity type to search.")): CustomAttributeMetadata @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\CustomAttributeMetadata") @doc(description: "Return the attribute type, given an attribute code and entity type.") @cache(cacheIdentity: "Magento\\EavGraphQl\\Model\\Resolver\\Cache\\CustomAttributeMetadataIdentity")
66
}
77

88
type CustomAttributeMetadata @doc(description: "Defines an array of custom attributes.") {
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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\StoreGraphQl\Model\Resolver\Stores;
9+
10+
use Magento\Framework\Exception\NoSuchEntityException;
11+
use Magento\Framework\GraphQl\Query\Resolver\IdentityInterface;
12+
use Magento\Store\Model\StoreManagerInterface;
13+
use Magento\StoreGraphQl\Model\Resolver\Store\ConfigIdentity as StoreConfigIdentity;
14+
15+
class ConfigIdentity implements IdentityInterface
16+
{
17+
/**
18+
* @var StoreManagerInterface
19+
*/
20+
private $storeManager;
21+
22+
/**
23+
* @param StoreManagerInterface $storeManager
24+
*/
25+
public function __construct(StoreManagerInterface $storeManager)
26+
{
27+
$this->storeManager = $storeManager;
28+
}
29+
30+
/**
31+
* @inheritDoc
32+
*/
33+
public function getIdentities(array $resolvedData): array
34+
{
35+
$ids = [];
36+
foreach ($resolvedData as $storeConfig) {
37+
$ids[] = sprintf('%s_%s', StoreConfigIdentity::CACHE_TAG, $storeConfig['id']);
38+
}
39+
if (!empty($resolvedData)) {
40+
$websiteId = $resolvedData[0]['website_id'];
41+
$currentStoreGroupId = $this->getCurrentStoreGroupId($resolvedData);
42+
$groupTag = $currentStoreGroupId ? 'group_' . $currentStoreGroupId : '';
43+
$ids[] = sprintf('%s_%s', StoreConfigIdentity::CACHE_TAG, 'website_' . $websiteId . $groupTag);
44+
}
45+
46+
return empty($ids) ? [] : array_merge([StoreConfigIdentity::CACHE_TAG], $ids);
47+
}
48+
49+
/**
50+
* Return current store group id if it is certain that useCurrentGroup is true in the query
51+
*
52+
* @param array $resolvedData
53+
* @return string|int|null
54+
*/
55+
private function getCurrentStoreGroupId(array $resolvedData)
56+
{
57+
$storeGroupCodes = array_unique(array_column($resolvedData, 'store_group_code'));
58+
if (count($storeGroupCodes) == 1) {
59+
try {
60+
$store = $this->storeManager->getStore($resolvedData[0]['id']);
61+
if ($store->getWebsite()->getGroupCollection()->count() != 1) {
62+
// There are multiple store groups in the website while there is only one store group
63+
// in the resolved data. Therefore useCurrentGroup must be true in the query
64+
return $store->getStoreGroupId();
65+
}
66+
} catch (NoSuchEntityException $e) {
67+
// Do nothing
68+
;
69+
}
70+
}
71+
return null;
72+
}
73+
}

app/code/Magento/StoreGraphQl/Plugin/Group.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use Magento\StoreGraphQl\Model\Resolver\Store\ConfigIdentity;
1111

1212
/**
13-
* Store group plugin
13+
* Store group plugin to provide identities for cache invalidation
1414
*/
1515
class Group
1616
{
@@ -24,9 +24,17 @@ class Group
2424
public function afterGetIdentities(\Magento\Store\Model\Group $subject, array $result): array
2525
{
2626
$storeIds = $subject->getStoreIds();
27-
foreach ($storeIds as $storeId) {
28-
$result[] = sprintf('%s_%s', ConfigIdentity::CACHE_TAG, $storeId);
27+
if (count($storeIds) > 0) {
28+
foreach ($storeIds as $storeId) {
29+
$result[] = sprintf('%s_%s', ConfigIdentity::CACHE_TAG, $storeId);
30+
}
31+
$origWebsiteId = $subject->getOrigData('website_id');
32+
$websiteId = $subject->getWebsiteId();
33+
if ($origWebsiteId != $websiteId) { // Add or switch to a new website
34+
$result[] = sprintf('%s_%s', ConfigIdentity::CACHE_TAG, 'website_' . $websiteId);
35+
}
2936
}
37+
3038
return $result;
3139
}
3240
}

app/code/Magento/StoreGraphQl/Plugin/Store.php

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use Magento\StoreGraphQl\Model\Resolver\Store\ConfigIdentity;
1111

1212
/**
13-
* Store plugin
13+
* Store plugin to provide identities for cache invalidation
1414
*/
1515
class Store
1616
{
@@ -23,6 +23,40 @@ class Store
2323
*/
2424
public function afterGetIdentities(\Magento\Store\Model\Store $subject, array $result): array
2525
{
26-
return array_merge($result, [sprintf('%s_%s', ConfigIdentity::CACHE_TAG, $subject->getId())]);
26+
$result[] = sprintf('%s_%s', ConfigIdentity::CACHE_TAG, $subject->getId());
27+
28+
$isActive = $subject->getIsActive();
29+
// New active store or newly activated store or an active store switched store group
30+
if ($isActive
31+
&& ($subject->getOrigData('is_active') !== $isActive || $this->isStoreGroupSwitched($subject))
32+
) {
33+
$websiteId = $subject->getWebsiteId();
34+
if ($websiteId !== null) {
35+
$result[] = sprintf('%s_%s', ConfigIdentity::CACHE_TAG, 'website_' . $websiteId);
36+
$storeGroupId = $subject->getStoreGroupId();
37+
if ($storeGroupId !== null) {
38+
$result[] = sprintf(
39+
'%s_%s',
40+
ConfigIdentity::CACHE_TAG,
41+
'website_' . $websiteId . 'group_' . $storeGroupId
42+
);
43+
}
44+
}
45+
}
46+
47+
return $result;
48+
}
49+
50+
/**
51+
* Check whether the store group of the store is switched
52+
*
53+
* @param \Magento\Store\Model\Store $store
54+
* @return bool
55+
*/
56+
private function isStoreGroupSwitched(\Magento\Store\Model\Store $store): bool
57+
{
58+
$origStoreGroupId = $store->getOrigData('group_id');
59+
$storeGroupId = $store->getStoreGroupId();
60+
return $origStoreGroupId != null && $origStoreGroupId != $storeGroupId;
2761
}
2862
}

app/code/Magento/StoreGraphQl/Plugin/Website.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use Magento\StoreGraphQl\Model\Resolver\Store\ConfigIdentity;
1111

1212
/**
13-
* Website plugin
13+
* Website plugin to provide identities for cache invalidation
1414
*/
1515
class Website
1616
{

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ type Query {
44
storeConfig : StoreConfig @resolver(class: "Magento\\StoreGraphQl\\Model\\Resolver\\StoreConfigResolver") @doc(description: "Return details about the store's configuration.") @cache(cacheIdentity: "Magento\\StoreGraphQl\\Model\\Resolver\\Store\\ConfigIdentity")
55
availableStores(
66
useCurrentGroup: Boolean @doc(description: "Filter store views by the current store group.")
7-
): [StoreConfig] @resolver(class: "Magento\\StoreGraphQl\\Model\\Resolver\\AvailableStoresResolver") @doc(description: "Get a list of available store views and their config information.")
7+
): [StoreConfig] @resolver(class: "Magento\\StoreGraphQl\\Model\\Resolver\\AvailableStoresResolver") @doc(description: "Get a list of available store views and their config information.") @cache(cacheIdentity: "Magento\\StoreGraphQl\\Model\\Resolver\\Stores\\ConfigIdentity")
88
}
99

1010
type Website @doc(description: "Deprecated. It should not be used on the storefront. Contains information about a website.") {

dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,15 @@ public function get(string $query, array $variables = [], string $operationName
101101
}
102102

103103
/**
104-
* Process response from GraphQl server
104+
* Process response from GraphQL server.
105105
*
106106
* @param string $response
107+
* @param array $responseHeaders
108+
* @param array $responseCookies
107109
* @return mixed
108110
* @throws \Exception
109111
*/
110-
private function processResponse(string $response)
112+
private function processResponse(string $response, array $responseHeaders = [], array $responseCookies = [])
111113
{
112114
$responseArray = $this->json->jsonDecode($response);
113115

@@ -116,7 +118,7 @@ private function processResponse(string $response)
116118
throw new \Exception('Unknown GraphQL response body: ' . $response);
117119
}
118120

119-
$this->processErrors($responseArray);
121+
$this->processErrors($responseArray, $responseHeaders, $responseCookies);
120122

121123
if (!isset($responseArray['data'])) {
122124
//phpcs:ignore Magento2.Exceptions.DirectThrow
@@ -153,9 +155,9 @@ public function getWithResponseHeaders(
153155
array_filter($requestArray);
154156

155157
$response = $this->curlClient->getWithFullResponse($url, $requestArray, $headers, $flushCookies);
156-
$responseBody = $this->processResponse($response['body']);
157158
$responseHeaders = !empty($response['header']) ? $this->processResponseHeaders($response['header']) : [];
158159
$responseCookies = !empty($response['header']) ? $this->processResponseCookies($response['header']) : [];
160+
$responseBody = $this->processResponse($response['body'], $responseHeaders, $responseCookies);
159161

160162
return ['headers' => $responseHeaders, 'body' => $responseBody, 'cookies' => $responseCookies];
161163
}
@@ -188,20 +190,23 @@ public function postWithResponseHeaders(
188190
$postData = $this->json->jsonEncode($requestArray);
189191

190192
$response = $this->curlClient->postWithFullResponse($url, $postData, $headers, $flushCookies);
191-
$responseBody = $this->processResponse($response['body']);
192193
$responseHeaders = !empty($response['header']) ? $this->processResponseHeaders($response['header']) : [];
193194
$responseCookies = !empty($response['header']) ? $this->processResponseCookies($response['header']) : [];
195+
$responseBody = $this->processResponse($response['body'], $responseHeaders, $responseCookies);
194196

195197
return ['headers' => $responseHeaders, 'body' => $responseBody, 'cookies' => $responseCookies];
196198
}
197199

198200
/**
199-
* Process errors
201+
* Process errors.
200202
*
201203
* @param array $responseBodyArray
202-
* @throws \Exception
204+
* @param array $responseHeaders
205+
* @param array $responseCookies
206+
* @return void
207+
* @throws ResponseContainsErrorsException
203208
*/
204-
private function processErrors($responseBodyArray)
209+
private function processErrors($responseBodyArray, array $responseHeaders = [], array $responseCookies = [])
205210
{
206211
if (isset($responseBodyArray['errors'])) {
207212
$errorMessage = '';
@@ -221,7 +226,11 @@ private function processErrors($responseBodyArray)
221226

222227
throw new ResponseContainsErrorsException(
223228
'GraphQL response contains errors: ' . $errorMessage,
224-
$responseBodyArray
229+
$responseBodyArray,
230+
null,
231+
0,
232+
$responseHeaders,
233+
$responseCookies
225234
);
226235
}
227236
//phpcs:ignore Magento2.Exceptions.DirectThrow

0 commit comments

Comments
 (0)