Skip to content

Commit e43a97b

Browse files
committed
Merge branch 'ACP2E-2642' of https://github.com/magento-l3/magento2ce into L3-PR-2024-02-16
2 parents 4356876 + 6f20e7c commit e43a97b

File tree

4 files changed

+273
-0
lines changed

4 files changed

+273
-0
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
/************************************************************************
3+
*
4+
* Copyright 2023 Adobe
5+
* All Rights Reserved.
6+
*
7+
* NOTICE: All information contained herein is, and remains
8+
* the property of Adobe and its suppliers, if any. The intellectual
9+
* and technical concepts contained herein are proprietary to Adobe
10+
* and its suppliers and are protected by all applicable intellectual
11+
* property laws, including trade secret and copyright laws.
12+
* Dissemination of this information or reproduction of this material
13+
* is strictly forbidden unless prior written permission is obtained
14+
* from Adobe.
15+
* ************************************************************************
16+
*/
17+
declare(strict_types=1);
18+
19+
namespace Magento\Cms\Test\Fixture;
20+
21+
use Magento\Cms\Api\BlockRepositoryInterface;
22+
use Magento\Cms\Api\Data\BlockInterface;
23+
use Magento\Framework\DataObject;
24+
use Magento\TestFramework\Fixture\Api\ServiceFactory;
25+
use Magento\TestFramework\Fixture\Data\ProcessorInterface;
26+
use Magento\TestFramework\Fixture\RevertibleDataFixtureInterface;
27+
28+
class Block implements RevertibleDataFixtureInterface
29+
{
30+
private const DEFAULT_DATA = [
31+
BlockInterface::IDENTIFIER => 'block%uniqid%',
32+
BlockInterface::TITLE => 'Block%uniqid%',
33+
BlockInterface::CONTENT => 'BlockContent%uniqid%',
34+
BlockInterface::CREATION_TIME => null,
35+
BlockInterface::UPDATE_TIME => null,
36+
'active' => true
37+
];
38+
39+
/**
40+
* @param ProcessorInterface $dataProcessor
41+
* @param ServiceFactory $serviceFactory
42+
*/
43+
public function __construct(
44+
private readonly ProcessorInterface $dataProcessor,
45+
private readonly ServiceFactory $serviceFactory
46+
) {
47+
}
48+
49+
/**
50+
* {@inheritdoc}
51+
* @param array $data Parameters. Same format as Block::DEFAULT_DATA.
52+
*/
53+
public function apply(array $data = []): ?DataObject
54+
{
55+
$data = $this->dataProcessor->process($this, array_merge(self::DEFAULT_DATA, $data));
56+
$service = $this->serviceFactory->create(BlockRepositoryInterface::class, 'save');
57+
58+
return $service->execute(['block' => $data]);
59+
}
60+
61+
/**
62+
* @inheritdoc
63+
*/
64+
public function revert(DataObject $data): void
65+
{
66+
$service = $this->serviceFactory->create(BlockRepositoryInterface::class, 'deleteById');
67+
$service->execute(['blockId' => $data->getId()]);
68+
}
69+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
/************************************************************************
3+
*
4+
* Copyright 2023 Adobe
5+
* All Rights Reserved.
6+
*
7+
* NOTICE: All information contained herein is, and remains
8+
* the property of Adobe and its suppliers, if any. The intellectual
9+
* and technical concepts contained herein are proprietary to Adobe
10+
* and its suppliers and are protected by all applicable intellectual
11+
* property laws, including trade secret and copyright laws.
12+
* Dissemination of this information or reproduction of this material
13+
* is strictly forbidden unless prior written permission is obtained
14+
* from Adobe.
15+
* ************************************************************************
16+
*/
17+
declare(strict_types=1);
18+
19+
namespace Magento\GraphQlCache\Model\Plugin\View;
20+
21+
use Magento\Framework\DataObject\IdentityInterface;
22+
use Magento\Framework\View\LayoutInterface;
23+
use Magento\GraphQlCache\Model\CacheableQuery;
24+
25+
class Layout
26+
{
27+
/**
28+
* @param CacheableQuery $cacheableQuery
29+
*/
30+
public function __construct(
31+
private readonly CacheableQuery $cacheableQuery
32+
) {
33+
}
34+
35+
/**
36+
* Add block cache tags to cacheable query
37+
*
38+
* @param LayoutInterface $subject
39+
* @param mixed $result
40+
* @param mixed $name
41+
* @param mixed $block
42+
* @return mixed
43+
*
44+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
45+
*/
46+
public function afterSetBlock(
47+
LayoutInterface $subject,
48+
mixed $result,
49+
mixed $name,
50+
mixed $block
51+
): mixed {
52+
if ($block instanceof IdentityInterface) {
53+
$this->cacheableQuery->addCacheTags($block->getIdentities());
54+
}
55+
return $result;
56+
}
57+
}

app/code/Magento/GraphQlCache/etc/graphql/di.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,7 @@
2929
<type name="Magento\Integration\Api\UserTokenRevokerInterface">
3030
<plugin name="set-guest-after-revoke" type="Magento\GraphQlCache\Model\Plugin\Auth\TokenRevoker"/>
3131
</type>
32+
<type name="Magento\Framework\View\LayoutInterface">
33+
<plugin name="add_block_cache_tags_to_query_cache" type="Magento\GraphQlCache\Model\Plugin\View\Layout"/>
34+
</type>
3235
</config>

dev/tests/api-functional/testsuite/Magento/GraphQl/PageCache/CacheTagTest.php

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,19 @@
77

88
namespace Magento\GraphQl\PageCache;
99

10+
use Magento\Catalog\Api\CategoryRepositoryInterface;
1011
use Magento\Catalog\Api\ProductRepositoryInterface;
1112
use Magento\Catalog\Model\Product;
13+
use Magento\Catalog\Test\Fixture\Category as CategoryFixture;
14+
use Magento\Cms\Model\BlockRepository;
15+
use Magento\Cms\Test\Fixture\Block as BlockFixture;
1216
use Magento\GraphQlCache\Model\CacheId\CacheIdCalculator;
17+
use Magento\PageCache\Model\Config;
18+
use Magento\Store\Model\Store;
19+
use Magento\TestFramework\Fixture\Config as ConfigFixture;
20+
use Magento\TestFramework\Fixture\DataFixture;
21+
use Magento\TestFramework\Fixture\DataFixtureStorageManager;
22+
use Magento\TestFramework\Helper\Bootstrap;
1323
use Magento\TestFramework\ObjectManager;
1424

1525
/**
@@ -133,6 +143,140 @@ public function testCacheInvalidationForCategoriesWithProduct()
133143
);
134144
}
135145

146+
#[
147+
ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH),
148+
DataFixture(BlockFixture::class, ['content' => 'Original Block content'], as: 'block'),
149+
DataFixture(CategoryFixture::class, as: 'category1'),
150+
DataFixture(CategoryFixture::class, ['description' => 'Original Category description'], as: 'category2'),
151+
]
152+
public function testCacheInvalidationForCategoriesWithWidget(): void
153+
{
154+
$fixtures = DataFixtureStorageManager::getStorage();
155+
$block = $fixtures->get('block');
156+
$category1 = $fixtures->get('category1');
157+
$category2 = $fixtures->get('category2');
158+
$queryForCategory1 = $this->getCategoriesQuery((int) $category1->getId());
159+
$queryForCategory2 = $this->getCategoriesQuery((int) $category2->getId());
160+
161+
$this->updateCategoryDescription((int) $category1->getId(), $this->getBlockWidget((int) $block->getId()));
162+
163+
$responseCacheIdForCategory1 = $this->getQueryResponseCacheKey($queryForCategory1);
164+
// Verify we get MISS for category1 query in the first request
165+
$responseMissForCategory1 = $this->assertCacheMissAndReturnResponse(
166+
$queryForCategory1,
167+
[CacheIdCalculator::CACHE_ID_HEADER => $responseCacheIdForCategory1]
168+
);
169+
170+
// Verify we get HIT for category 1 query in the second request
171+
$responseHitForCategory1 = $this->assertCacheHitAndReturnResponse(
172+
$queryForCategory1,
173+
[CacheIdCalculator::CACHE_ID_HEADER => $responseCacheIdForCategory1]
174+
);
175+
176+
$this->assertEquals($responseMissForCategory1['body'], $responseHitForCategory1['body']);
177+
178+
// Verify category1 description contains block content
179+
$this->assertCategoryDescription('Original Block content', $responseHitForCategory1);
180+
181+
$responseCacheIdForCategory2 = $this->getQueryResponseCacheKey($queryForCategory2);
182+
// Verify we get MISS for category2 query in the first request
183+
$responseMissForCategory2 = $this->assertCacheMissAndReturnResponse(
184+
$queryForCategory2,
185+
[CacheIdCalculator::CACHE_ID_HEADER => $responseCacheIdForCategory2]
186+
);
187+
188+
// Verify we get HIT for category 2 query in the second request
189+
$responseHitForCategory2 = $this->assertCacheHitAndReturnResponse(
190+
$queryForCategory2,
191+
[CacheIdCalculator::CACHE_ID_HEADER => $responseCacheIdForCategory2]
192+
);
193+
194+
$this->assertEquals($responseMissForCategory2['body'], $responseHitForCategory2['body']);
195+
196+
// Verify category2 description is the same as created
197+
$this->assertCategoryDescription('Original Category description', $responseHitForCategory2);
198+
199+
// Update block content
200+
$newBlockContent = 'New block content!!!';
201+
$this->updateBlockContent((int) $block->getId(), $newBlockContent);
202+
203+
// Verify we get MISS for category1 query after block is updated
204+
$this->assertCacheMissAndReturnResponse(
205+
$queryForCategory1,
206+
[CacheIdCalculator::CACHE_ID_HEADER => $responseCacheIdForCategory1]
207+
);
208+
209+
// Verify we get HIT for category1 query in the second request after block is updated
210+
$responseHitForCategory1 = $this->assertCacheHitAndReturnResponse(
211+
$queryForCategory1,
212+
[CacheIdCalculator::CACHE_ID_HEADER => $responseCacheIdForCategory1]
213+
);
214+
215+
// Verify we get HIT for category2 query after block is updated
216+
$responseHitForCategory2 = $this->assertCacheHitAndReturnResponse(
217+
$queryForCategory2,
218+
[CacheIdCalculator::CACHE_ID_HEADER => $responseCacheIdForCategory2]
219+
);
220+
221+
// Verify the updated block data is returned in category1 query response
222+
$this->assertCategoryDescription($newBlockContent, $responseHitForCategory1);
223+
224+
// Verify category2 description is the same as created
225+
$this->assertCategoryDescription('Original Category description', $responseHitForCategory2);
226+
}
227+
228+
private function assertCategoryDescription(string $expected, array $response): void
229+
{
230+
$responseBody = $response['body'];
231+
$this->assertIsArray($responseBody);
232+
$this->assertArrayNotHasKey('errors', $responseBody);
233+
$this->assertStringContainsString($expected, $responseBody['categories']['items'][0]['description']);
234+
}
235+
236+
private function getQueryResponseCacheKey(string $query): string
237+
{
238+
$response = $this->graphQlQueryWithResponseHeaders($query);
239+
$this->assertArrayHasKey(CacheIdCalculator::CACHE_ID_HEADER, $response['headers']);
240+
return $response['headers'][CacheIdCalculator::CACHE_ID_HEADER];
241+
}
242+
243+
private function updateBlockContent(int $id, string $content): void
244+
{
245+
$blockRepository = Bootstrap::getObjectManager()->get(BlockRepository::class);
246+
$block = $blockRepository->getById($id);
247+
$block->setContent($content);
248+
$blockRepository->save($block);
249+
}
250+
251+
private function updateCategoryDescription(int $id, string $description): void
252+
{
253+
$categoryRepository = Bootstrap::getObjectManager()->get(CategoryRepositoryInterface::class);
254+
$category = $categoryRepository->get($id, Store::DEFAULT_STORE_ID);
255+
$category->setCustomAttribute('description', $description);
256+
$categoryRepository->save($category);
257+
}
258+
259+
private function getBlockWidget(int $blockId): string
260+
{
261+
return "{{widget type=\"Magento\\Cms\\Block\\Widget\\Block\" " .
262+
"template=\"widget/static_block/default.phtml\" " .
263+
"block_id=\"$blockId\" " .
264+
"type_name=\"CMS Static Block\"}}";
265+
}
266+
267+
private function getCategoriesQuery(int $categoryId): string
268+
{
269+
return <<<QUERY
270+
{
271+
categories(filters: {ids: {in: ["$categoryId"]}}) {
272+
items{
273+
description
274+
}
275+
}
276+
}
277+
QUERY;
278+
}
279+
136280
/**
137281
* Get Product query
138282
*

0 commit comments

Comments
 (0)