Skip to content

Commit 660c989

Browse files
committed
Fix loss of page_cache cache_dir setting from di.xml
Additionally fixed follow up issues: * Clean category pages from the built in page cache when categories are moved * Improve test isolation for GraphQl controller tests * Add tests to check caching queries actually works
1 parent 7ffabd0 commit 660c989

File tree

13 files changed

+537
-403
lines changed

13 files changed

+537
-403
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php declare(strict_types=1);
2+
/**
3+
*
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
8+
namespace Magento\Catalog\Observer;
9+
10+
use Magento\Catalog\Model\Category;
11+
use Magento\Framework\Event\Observer as Event;
12+
use Magento\Framework\Event\ObserverInterface;
13+
use Magento\PageCache\Model\Cache\Type as PageCache;
14+
use Magento\PageCache\Model\Config as CacheConfig;
15+
16+
/**
17+
* Flush the built in page cache when a category is moved
18+
*/
19+
class FlushCategoryPagesCache implements ObserverInterface
20+
{
21+
22+
/**
23+
* @var CacheConfig
24+
*/
25+
private $cacheConfig;
26+
27+
/**
28+
*
29+
* @var PageCache
30+
*/
31+
private $pageCache;
32+
33+
/**
34+
* FlushCategoryPagesCache constructor.
35+
*
36+
* @param CacheConfig $cacheConfig
37+
* @param PageCache $pageCache
38+
*/
39+
public function __construct(CacheConfig $cacheConfig, PageCache $pageCache)
40+
{
41+
$this->cacheConfig = $cacheConfig;
42+
$this->pageCache = $pageCache;
43+
}
44+
45+
/**
46+
* Clean the category page cache if built in cache page cache is used.
47+
*
48+
* The built in cache requires cleaning all pages that contain the top category navigation menu when a
49+
* category is moved. This is because the built in cache does not support ESI blocks.
50+
*
51+
* @param Event $event
52+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
53+
*/
54+
public function execute(Event $event)
55+
{
56+
if ($this->cacheConfig->getType() == CacheConfig::BUILT_IN && $this->cacheConfig->isEnabled()) {
57+
$this->pageCache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, [Category::CACHE_TAG]);
58+
}
59+
}
60+
}

app/code/Magento/Catalog/etc/adminhtml/events.xml

+3
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@
1212
<event name="catalog_category_change_products">
1313
<observer name="category_product_indexer" instance="Magento\Catalog\Observer\CategoryProductIndexer"/>
1414
</event>
15+
<event name="category_move">
16+
<observer name="clean_cagegory_page_cache" instance="Magento\Catalog\Observer\FlushCategoryPagesCache" />
17+
</event>
1518
</config>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php declare(strict_types=1);
2+
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
8+
namespace Magento\Framework\App\Cache\Frontend;
9+
10+
use Magento\Framework\ObjectManager\ConfigInterface as ObjectManagerConfig;
11+
use Magento\TestFramework\ObjectManager;
12+
use PHPUnit\Framework\TestCase;
13+
14+
/**
15+
* This superfluous comment can be removed as soon as the sniffs have been updated to match the coding guide lines.
16+
*/
17+
class PoolTest extends TestCase
18+
{
19+
public function testPageCacheNotSameAsDefaultCacheDirectory(): void
20+
{
21+
/** @var ObjectManagerConfig $diConfig */
22+
$diConfig = ObjectManager::getInstance()->get(ObjectManagerConfig::class);
23+
$argumentConfig = $diConfig->getArguments(\Magento\Framework\App\Cache\Frontend\Pool::class);
24+
25+
$pageCacheDir = $argumentConfig['frontendSettings']['page_cache']['backend_options']['cache_dir'] ?? null;
26+
$defaultCacheDir = $argumentConfig['frontendSettings']['default']['backend_options']['cache_dir'] ?? null;
27+
28+
$noPageCacheMessage = "No default page_cache directory set in di.xml: \n" . var_export($argumentConfig, true);
29+
$this->assertNotEmpty($pageCacheDir, $noPageCacheMessage);
30+
31+
$sameCacheDirMessage = 'The page_cache and default cache storages share the same cache directory';
32+
$this->assertNotSame($pageCacheDir, $defaultCacheDir, $sameCacheDirMessage);
33+
}
34+
35+
/**
36+
* @covers \Magento\Framework\App\Cache\Frontend\Pool::_getCacheSettings
37+
* @depends testPageCacheNotSameAsDefaultCacheDirectory
38+
*/
39+
public function testCleaningDefaultCachePreservesPageCache()
40+
{
41+
$testData = 'test data';
42+
$testKey = 'test-key';
43+
44+
/** @var \Magento\Framework\App\Cache\Frontend\Pool $cacheFrontendPool */
45+
$cacheFrontendPool = ObjectManager::getInstance()->get(\Magento\Framework\App\Cache\Frontend\Pool::class);
46+
47+
$pageCache = $cacheFrontendPool->get('page_cache');
48+
$pageCache->save($testData, $testKey);
49+
50+
$defaultCache = $cacheFrontendPool->get('default');
51+
$defaultCache->clean();
52+
53+
$this->assertSame($testData, $pageCache->load($testKey));
54+
}
55+
}

dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/AbstractGraphqlCacheTest.php

+108-28
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@
77

88
namespace Magento\GraphQlCache\Controller;
99

10-
use PHPUnit\Framework\TestCase;
11-
use Magento\TestFramework\ObjectManager;
10+
use Magento\Framework\App\Request\Http as HttpRequest;
11+
use Magento\Framework\App\Response\HttpInterface as HttpResponse;
12+
use Magento\Framework\Registry;
13+
use Magento\GraphQl\Controller\GraphQl as GraphQlController;
14+
use Magento\GraphQlCache\Model\CacheableQuery;
15+
use Magento\PageCache\Model\Cache\Type as PageCache;
1216
use Magento\TestFramework\Helper\Bootstrap;
17+
use Magento\TestFramework\ObjectManager;
18+
use PHPUnit\Framework\TestCase;
1319

1420
/**
1521
* Abstract test class for Graphql cache tests
@@ -21,40 +27,114 @@ abstract class AbstractGraphqlCacheTest extends TestCase
2127
*/
2228
protected $objectManager;
2329

24-
/**
25-
* @inheritdoc
26-
*/
2730
protected function setUp(): void
2831
{
2932
$this->objectManager = Bootstrap::getObjectManager();
33+
$this->enablePageCachePlugin();
34+
$this->enableCachebleQueryTestProxy();
35+
}
36+
37+
protected function tearDown(): void
38+
{
39+
$this->disableCacheableQueryTestProxy();
40+
$this->disablePageCachePlugin();
41+
$this->flushPageCache();
42+
}
43+
44+
protected function enablePageCachePlugin(): void
45+
{
46+
/** @var $registry Registry */
47+
$registry = $this->objectManager->get(Registry::class);
48+
$registry->register('use_page_cache_plugin', true, true);
49+
}
50+
51+
protected function disablePageCachePlugin(): void
52+
{
53+
/** @var $registry Registry */
54+
$registry = $this->objectManager->get(Registry::class);
55+
$registry->unregister('use_page_cache_plugin');
56+
}
57+
58+
protected function flushPageCache(): void
59+
{
60+
/** @var PageCache $fullPageCache */
61+
$fullPageCache = $this->objectManager->get(PageCache::class);
62+
$fullPageCache->clean();
3063
}
3164

3265
/**
33-
* Prepare a query and return a request to be used in the same test end to end
66+
* Regarding the SuppressWarnings annotation below: the nested class below triggers a false rule match.
3467
*
35-
* @param string $query
36-
* @return \Magento\Framework\App\Request\Http
68+
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
3769
*/
38-
protected function prepareRequest(string $query) : \Magento\Framework\App\Request\Http
39-
{
40-
$cacheableQuery = $this->objectManager->get(\Magento\GraphQlCache\Model\CacheableQuery::class);
41-
$cacheableQueryReflection = new \ReflectionProperty(
42-
$cacheableQuery,
43-
'cacheTags'
44-
);
45-
$cacheableQueryReflection->setAccessible(true);
46-
$cacheableQueryReflection->setValue($cacheableQuery, []);
47-
48-
/** @var \Magento\Framework\UrlInterface $urlInterface */
49-
$urlInterface = $this->objectManager->create(\Magento\Framework\UrlInterface::class);
50-
//set unique URL
51-
$urlInterface->setQueryParam('query', $query);
52-
53-
$request = $this->objectManager->get(\Magento\Framework\App\Request\Http::class);
54-
$request->setUri($urlInterface->getUrl('graphql'));
70+
private function enableCachebleQueryTestProxy(): void
71+
{
72+
$cacheableQueryProxy = new class($this->objectManager) extends CacheableQuery {
73+
/** @var CacheableQuery */
74+
private $delegate;
75+
76+
public function __construct(ObjectManager $objectManager)
77+
{
78+
$this->reset($objectManager);
79+
}
80+
81+
public function reset(ObjectManager $objectManager): void
82+
{
83+
$this->delegate = $objectManager->create(CacheableQuery::class);
84+
}
85+
86+
public function getCacheTags(): array
87+
{
88+
return $this->delegate->getCacheTags();
89+
}
90+
91+
public function addCacheTags(array $cacheTags): void
92+
{
93+
$this->delegate->addCacheTags($cacheTags);
94+
}
95+
96+
public function isCacheable(): bool
97+
{
98+
return $this->delegate->isCacheable();
99+
}
100+
101+
public function setCacheValidity(bool $cacheable): void
102+
{
103+
$this->delegate->setCacheValidity($cacheable);
104+
}
105+
106+
public function shouldPopulateCacheHeadersWithTags(): bool
107+
{
108+
return $this->delegate->shouldPopulateCacheHeadersWithTags();
109+
}
110+
};
111+
$this->objectManager->addSharedInstance($cacheableQueryProxy, CacheableQuery::class);
112+
}
113+
114+
private function disableCacheableQueryTestProxy(): void
115+
{
116+
$this->resetQueryCacheTags();
117+
$this->objectManager->removeSharedInstance(CacheableQuery::class);
118+
}
119+
120+
protected function resetQueryCacheTags(): void
121+
{
122+
$this->objectManager->get(CacheableQuery::class)->reset($this->objectManager);
123+
}
124+
125+
protected function dispatchGraphQlGETRequest(array $queryParams): HttpResponse
126+
{
127+
$this->resetQueryCacheTags();
128+
129+
/** @var HttpRequest $request */
130+
$request = $this->objectManager->get(HttpRequest::class);
131+
$request->setPathInfo('/graphql');
55132
$request->setMethod('GET');
56-
//set the actual GET query
57-
$request->setQueryValue('query', $query);
58-
return $request;
133+
$request->setParams($queryParams);
134+
135+
// required for \Magento\Framework\App\PageCache\Identifier to generate the correct cache key
136+
$request->setUri(implode('?', [$request->getPathInfo(), http_build_query($queryParams)]));
137+
138+
return $this->objectManager->create(GraphQlController::class)->dispatch($request);
59139
}
60140
}

dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Catalog/CategoriesWithProductsCacheTest.php

+2-33
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99

1010
use Magento\Catalog\Api\Data\ProductInterface;
1111
use Magento\Catalog\Api\ProductRepositoryInterface;
12-
use Magento\Framework\App\Request\Http;
13-
use Magento\GraphQl\Controller\GraphQl;
1412
use Magento\GraphQlCache\Controller\AbstractGraphqlCacheTest;
1513

1614
/**
@@ -22,31 +20,12 @@
2220
*/
2321
class CategoriesWithProductsCacheTest extends AbstractGraphqlCacheTest
2422
{
25-
/**
26-
* @var GraphQl
27-
*/
28-
private $graphqlController;
29-
30-
/**
31-
* @var Http
32-
*/
33-
private $request;
34-
35-
/**
36-
* @inheritdoc
37-
*/
38-
protected function setUp(): void
39-
{
40-
parent::setUp();
41-
$this->graphqlController = $this->objectManager->get(\Magento\GraphQl\Controller\GraphQl::class);
42-
$this->request = $this->objectManager->create(Http::class);
43-
}
4423
/**
4524
* Test cache tags and debug header for category with products querying for products and category
4625
*
4726
* @magentoDataFixture Magento/Catalog/_files/category_product.php
4827
*/
49-
public function testToCheckRequestCacheTagsForCategoryWithProducts(): void
28+
public function testRequestCacheTagsForCategoryWithProducts(): void
5029
{
5130
/** @var ProductRepositoryInterface $productRepository */
5231
$productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
@@ -91,17 +70,7 @@ public function testToCheckRequestCacheTagsForCategoryWithProducts(): void
9170
'operationName' => 'GetCategoryWithProducts'
9271
];
9372

94-
/** @var \Magento\Framework\UrlInterface $urlInterface */
95-
$urlInterface = $this->objectManager->create(\Magento\Framework\UrlInterface::class);
96-
//set unique URL
97-
$urlInterface->setQueryParam('query', $queryParams['query']);
98-
$urlInterface->setQueryParam('variables', $queryParams['variables']);
99-
$urlInterface->setQueryParam('operationName', $queryParams['operationName']);
100-
$this->request->setUri($urlInterface->getUrl('graphql'));
101-
$this->request->setPathInfo('/graphql');
102-
$this->request->setMethod('GET');
103-
$this->request->setParams($queryParams);
104-
$response = $this->graphqlController->dispatch($this->request);
73+
$response = $this->dispatchGraphQlGETRequest($queryParams);
10574
$this->assertEquals('MISS', $response->getHeader('X-Magento-Cache-Debug')->getFieldValue());
10675
$expectedCacheTags = ['cat_c','cat_c_' . $categoryId,'cat_p','cat_p_' . $product->getId(),'FPC'];
10776
$actualCacheTags = explode(',', $response->getHeader('X-Magento-Tags')->getFieldValue());

dev/tests/integration/testsuite/Magento/GraphQlCache/Controller/Catalog/CategoryCacheTest.php

+3-19
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77

88
namespace Magento\GraphQlCache\Controller\Catalog;
99

10-
use Magento\Framework\App\Request\Http;
11-
use Magento\GraphQl\Controller\GraphQl;
1210
use Magento\GraphQlCache\Controller\AbstractGraphqlCacheTest;
1311

1412
/**
@@ -20,25 +18,12 @@
2018
*/
2119
class CategoryCacheTest extends AbstractGraphqlCacheTest
2220
{
23-
/**
24-
* @var GraphQl
25-
*/
26-
private $graphqlController;
27-
28-
/**
29-
* @inheritdoc
30-
*/
31-
protected function setUp(): void
32-
{
33-
parent::setUp();
34-
$this->graphqlController = $this->objectManager->get(\Magento\GraphQl\Controller\GraphQl::class);
35-
}
3621
/**
3722
* Test cache tags and debug header for category and querying only for category
3823
*
3924
* @magentoDataFixture Magento/Catalog/_files/category_product.php
4025
*/
41-
public function testToCheckRequestCacheTagsForForCategory(): void
26+
public function testRequestCacheTagsForCategory(): void
4227
{
4328
$categoryId ='333';
4429
$query
@@ -53,11 +38,10 @@ public function testToCheckRequestCacheTagsForForCategory(): void
5338
}
5439
}
5540
QUERY;
56-
$request = $this->prepareRequest($query);
57-
$response = $this->graphqlController->dispatch($request);
41+
$response = $this->dispatchGraphQlGETRequest(['query' => $query]);
5842
$this->assertEquals('MISS', $response->getHeader('X-Magento-Cache-Debug')->getFieldValue());
5943
$actualCacheTags = explode(',', $response->getHeader('X-Magento-Tags')->getFieldValue());
60-
$expectedCacheTags = ['cat_c','cat_c_' . $categoryId,'FPC'];
44+
$expectedCacheTags = ['cat_c','cat_c_' . $categoryId, 'FPC'];
6145
$this->assertEquals($expectedCacheTags, $actualCacheTags);
6246
}
6347
}

0 commit comments

Comments
 (0)