Skip to content

Commit 0e37353

Browse files
author
Mastiuhin Olexandr
committed
MAGETWO-91607: Incorrect page caching with enabled maintenance mode when accessing from the exclude IP list.
1 parent f211371 commit 0e37353

File tree

7 files changed

+572
-59
lines changed

7 files changed

+572
-59
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
/**
3+
*
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Magento\PageCache\Observer;
11+
12+
use Magento\Framework\Event\ObserverInterface;
13+
use Magento\Framework\Event\Observer;
14+
use Magento\Framework\App\Cache\Manager;
15+
use Magento\PageCache\Model\Cache\Type as PageCacheType;
16+
use Magento\PageCache\Observer\SwitchPageCacheOnMaintenance\PageCacheState;
17+
18+
/**
19+
* Switch Page Cache on maintenance.
20+
*/
21+
class SwitchPageCacheOnMaintenance implements ObserverInterface
22+
{
23+
/**
24+
* @var Manager
25+
*/
26+
private $cacheManager;
27+
28+
/**
29+
* @var PageCacheState
30+
*/
31+
private $pageCacheStateStorage;
32+
33+
/**
34+
* @param Manager $cacheManager
35+
* @param PageCacheState $pageCacheStateStorage
36+
*/
37+
public function __construct(Manager $cacheManager, PageCacheState $pageCacheStateStorage)
38+
{
39+
$this->cacheManager = $cacheManager;
40+
$this->pageCacheStateStorage = $pageCacheStateStorage;
41+
}
42+
43+
/**
44+
* Switches Full Page Cache.
45+
*
46+
* Depending on enabling or disabling Maintenance Mode it turns off or restores Full Page Cache state.
47+
*
48+
* @param Observer $observer
49+
* @return void
50+
*/
51+
public function execute(Observer $observer): void
52+
{
53+
if ($observer->getData('isOn')) {
54+
$this->pageCacheStateStorage->save($this->isFullPageCacheEnabled());
55+
$this->turnOffFullPageCache();
56+
} else {
57+
$this->restoreFullPageCacheState();
58+
}
59+
}
60+
61+
/**
62+
* Turns off Full Page Cache.
63+
*
64+
* @return void
65+
*/
66+
private function turnOffFullPageCache(): void
67+
{
68+
if (!$this->isFullPageCacheEnabled()) {
69+
return;
70+
}
71+
72+
$this->cacheManager->clean([PageCacheType::TYPE_IDENTIFIER]);
73+
$this->cacheManager->setEnabled([PageCacheType::TYPE_IDENTIFIER], false);
74+
}
75+
76+
/**
77+
* Full Page Cache state.
78+
*
79+
* @return bool
80+
*/
81+
private function isFullPageCacheEnabled(): bool
82+
{
83+
$cacheStatus = $this->cacheManager->getStatus();
84+
85+
if (!array_key_exists(PageCacheType::TYPE_IDENTIFIER, $cacheStatus)) {
86+
return false;
87+
}
88+
89+
return (bool)$cacheStatus[PageCacheType::TYPE_IDENTIFIER];
90+
}
91+
92+
/**
93+
* Restores Full Page Cache state.
94+
*
95+
* Returns FPC to previous state that was before maintenance mode turning on.
96+
*
97+
* @return void
98+
*/
99+
private function restoreFullPageCacheState(): void
100+
{
101+
$storedPageCacheState = $this->pageCacheStateStorage->isEnabled();
102+
$this->pageCacheStateStorage->flush();
103+
104+
if ($storedPageCacheState) {
105+
$this->cacheManager->setEnabled([PageCacheType::TYPE_IDENTIFIER], true);
106+
}
107+
}
108+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
/**
3+
*
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Magento\PageCache\Observer\SwitchPageCacheOnMaintenance;
11+
12+
use Magento\Framework\Filesystem;
13+
use Magento\Framework\App\Filesystem\DirectoryList;
14+
15+
/**
16+
* Page Cache state.
17+
*/
18+
class PageCacheState
19+
{
20+
/**
21+
* Full Page Cache Off state file name.
22+
*/
23+
private const PAGE_CACHE_STATE_FILENAME = '.maintenance.fpc.state';
24+
25+
/**
26+
* @var Filesystem\Directory\WriteInterface
27+
*/
28+
private $flagDir;
29+
30+
/**
31+
* @param Filesystem $fileSystem
32+
*/
33+
public function __construct(Filesystem $fileSystem)
34+
{
35+
$this->flagDir = $fileSystem->getDirectoryWrite(DirectoryList::VAR_DIR);
36+
}
37+
38+
/**
39+
* Saves Full Page Cache state.
40+
*
41+
* Saves FPC state across requests.
42+
*
43+
* @param bool $state
44+
* @return void
45+
*/
46+
public function save(bool $state): void
47+
{
48+
$this->flagDir->writeFile(self::PAGE_CACHE_STATE_FILENAME, (string)$state);
49+
}
50+
51+
/**
52+
* Returns stored Full Page Cache state.
53+
*
54+
* @return bool
55+
*/
56+
public function isEnabled(): bool
57+
{
58+
if (!$this->flagDir->isExist(self::PAGE_CACHE_STATE_FILENAME)) {
59+
return false;
60+
}
61+
62+
return (bool)$this->flagDir->readFile(self::PAGE_CACHE_STATE_FILENAME);
63+
}
64+
65+
/**
66+
* Flushes Page Cache state storage.
67+
*
68+
* @return void
69+
*/
70+
public function flush(): void
71+
{
72+
$this->flagDir->delete(self::PAGE_CACHE_STATE_FILENAME);
73+
}
74+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
<?php
2+
/**
3+
*
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Magento\PageCache\Test\Unit\Observer;
11+
12+
use PHPUnit\Framework\TestCase;
13+
use Magento\PageCache\Observer\SwitchPageCacheOnMaintenance;
14+
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
15+
use Magento\Framework\App\Cache\Manager;
16+
use Magento\Framework\Event\Observer;
17+
use Magento\PageCache\Model\Cache\Type as PageCacheType;
18+
use Magento\PageCache\Observer\SwitchPageCacheOnMaintenance\PageCacheState;
19+
20+
/**
21+
* SwitchPageCacheOnMaintenance observer test.
22+
*/
23+
class SwitchPageCacheOnMaintenanceTest extends TestCase
24+
{
25+
/**
26+
* @var SwitchPageCacheOnMaintenance
27+
*/
28+
private $model;
29+
30+
/**
31+
* @var Manager|\PHPUnit\Framework\MockObject\MockObject
32+
*/
33+
private $cacheManager;
34+
35+
/**
36+
* @var PageCacheState|\PHPUnit\Framework\MockObject\MockObject
37+
*/
38+
private $pageCacheStateStorage;
39+
40+
/**
41+
* @var Observer|\PHPUnit\Framework\MockObject\MockObject
42+
*/
43+
private $observer;
44+
45+
/**
46+
* @inheritdoc
47+
*/
48+
protected function setUp(): void
49+
{
50+
$objectManager = new ObjectManager($this);
51+
$this->cacheManager = $this->createMock(Manager::class);
52+
$this->pageCacheStateStorage = $this->createMock(PageCacheState::class);
53+
$this->observer = $this->createMock(Observer::class);
54+
55+
$this->model = $objectManager->getObject(SwitchPageCacheOnMaintenance::class, [
56+
'cacheManager' => $this->cacheManager,
57+
'pageCacheStateStorage' => $this->pageCacheStateStorage,
58+
]);
59+
}
60+
61+
/**
62+
* Tests execute when setting maintenance mode to on.
63+
*
64+
* @param array $cacheStatus
65+
* @param bool $cacheState
66+
* @param int $flushCacheCalls
67+
* @return void
68+
* @dataProvider enablingPageCacheStateProvider
69+
*/
70+
public function testExecuteWhileMaintenanceEnabling(
71+
array $cacheStatus,
72+
bool $cacheState,
73+
int $flushCacheCalls
74+
): void {
75+
$this->observer->method('getData')
76+
->with('isOn')
77+
->willReturn(true);
78+
$this->cacheManager->method('getStatus')
79+
->willReturn($cacheStatus);
80+
81+
// Page Cache state will be stored.
82+
$this->pageCacheStateStorage->expects($this->once())
83+
->method('save')
84+
->with($cacheState);
85+
86+
// Page Cache will be cleaned and disabled
87+
$this->cacheManager->expects($this->exactly($flushCacheCalls))
88+
->method('clean')
89+
->with([PageCacheType::TYPE_IDENTIFIER]);
90+
$this->cacheManager->expects($this->exactly($flushCacheCalls))
91+
->method('setEnabled')
92+
->with([PageCacheType::TYPE_IDENTIFIER], false);
93+
94+
$this->model->execute($this->observer);
95+
}
96+
97+
/**
98+
* Tests execute when setting Maintenance Mode to off.
99+
*
100+
* @param bool $storedCacheState
101+
* @param int $enableCacheCalls
102+
* @return void
103+
* @dataProvider disablingPageCacheStateProvider
104+
*/
105+
public function testExecuteWhileMaintenanceDisabling(bool $storedCacheState, int $enableCacheCalls): void
106+
{
107+
$this->observer->method('getData')
108+
->with('isOn')
109+
->willReturn(false);
110+
111+
$this->pageCacheStateStorage->method('isEnabled')
112+
->willReturn($storedCacheState);
113+
114+
// Nullify Page Cache state.
115+
$this->pageCacheStateStorage->expects($this->once())
116+
->method('flush');
117+
118+
// Page Cache will be enabled.
119+
$this->cacheManager->expects($this->exactly($enableCacheCalls))
120+
->method('setEnabled')
121+
->with([PageCacheType::TYPE_IDENTIFIER]);
122+
123+
$this->model->execute($this->observer);
124+
}
125+
126+
/**
127+
* Page Cache state data provider.
128+
*
129+
* @return array
130+
*/
131+
public function enablingPageCacheStateProvider(): array
132+
{
133+
return [
134+
'page_cache_is_enable' => [
135+
'cache_status' => [PageCacheType::TYPE_IDENTIFIER => 1],
136+
'cache_state' => true,
137+
'flush_cache_calls' => 1,
138+
],
139+
'page_cache_is_missing_in_system' => [
140+
'cache_status' => [],
141+
'cache_state' => false,
142+
'flush_cache_calls' => 0,
143+
],
144+
'page_cache_is_disable' => [
145+
'cache_status' => [PageCacheType::TYPE_IDENTIFIER => 0],
146+
'cache_state' => false,
147+
'flush_cache_calls' => 0,
148+
],
149+
];
150+
}
151+
152+
/**
153+
* Page Cache state data provider.
154+
*
155+
* @return array
156+
*/
157+
public function disablingPageCacheStateProvider(): array
158+
{
159+
return [
160+
['stored_cache_state' => true, 'enable_cache_calls' => 1],
161+
['stored_cache_state' => false, 'enable_cache_calls' => 0],
162+
];
163+
}
164+
}

app/code/Magento/PageCache/etc/events.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,7 @@
5757
<event name="customer_logout">
5858
<observer name="FlushFormKey" instance="Magento\PageCache\Observer\FlushFormKey"/>
5959
</event>
60+
<event name="maintenance_mode_changed">
61+
<observer name="page_cache_switcher_for_maintenance" instance="Magento\PageCache\Observer\SwitchPageCacheOnMaintenance"/>
62+
</event>
6063
</config>

0 commit comments

Comments
 (0)