Skip to content

Commit f72db1b

Browse files
committed
MAGETWO-80647: Fix PageCache: async rendering of blocks can corrupt layout cache #8554 #9050 #11174
- Merge Pull Request #11174 from adrian-martinez-interactiv4/magento2:FR#FIX-PAGECACHE-LAYOUT-CACHE-KEY - Merged commits: 1. 3bdfa1b 2. 5a6a000 3. bf7df0d 4. 7feba6c 5. 1bb25ed 6. 0e830c1 7. 8a92e46 8. 0a69b5b 9. bd6ed7c 10. c6e2b39 11. 3cc51dc 12. 2ae9c26 13. 516b529 14. efa3edb 15. 3f60168
2 parents 205a408 + 3f60168 commit f72db1b

File tree

12 files changed

+197
-13
lines changed

12 files changed

+197
-13
lines changed

app/code/Magento/PageCache/Controller/Block.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
use Magento\Framework\Serialize\Serializer\Base64Json;
1111
use Magento\Framework\Serialize\Serializer\Json;
12+
use Magento\Framework\View\Layout\LayoutCacheKeyInterface;
1213

1314
abstract class Block extends \Magento\Framework\App\Action\Action
1415
{
@@ -27,24 +28,40 @@ abstract class Block extends \Magento\Framework\App\Action\Action
2728
*/
2829
private $base64jsonSerializer;
2930

31+
/**
32+
* Layout cache keys to be able to generate different cache id for same handles
33+
*
34+
* @var LayoutCacheKeyInterface
35+
*/
36+
private $layoutCacheKey;
37+
38+
/**
39+
* @var string
40+
*/
41+
private $layoutCacheKeyName = 'mage_pagecache';
42+
3043
/**
3144
* @param \Magento\Framework\App\Action\Context $context
3245
* @param \Magento\Framework\Translate\InlineInterface $translateInline
3346
* @param Json $jsonSerializer
3447
* @param Base64Json $base64jsonSerializer
48+
* @param LayoutCacheKeyInterface $layoutCacheKey
3549
*/
3650
public function __construct(
3751
\Magento\Framework\App\Action\Context $context,
3852
\Magento\Framework\Translate\InlineInterface $translateInline,
3953
Json $jsonSerializer = null,
40-
Base64Json $base64jsonSerializer = null
54+
Base64Json $base64jsonSerializer = null,
55+
LayoutCacheKeyInterface $layoutCacheKey = null
4156
) {
4257
parent::__construct($context);
4358
$this->translateInline = $translateInline;
4459
$this->jsonSerializer = $jsonSerializer
4560
?: \Magento\Framework\App\ObjectManager::getInstance()->get(Json::class);
4661
$this->base64jsonSerializer = $base64jsonSerializer
4762
?: \Magento\Framework\App\ObjectManager::getInstance()->get(Base64Json::class);
63+
$this->layoutCacheKey = $layoutCacheKey
64+
?: \Magento\Framework\App\ObjectManager::getInstance()->get(LayoutCacheKeyInterface::class);
4865
}
4966

5067
/**
@@ -63,10 +80,12 @@ protected function _getBlocks()
6380
$blocks = $this->jsonSerializer->unserialize($blocks);
6481
$handles = $this->base64jsonSerializer->unserialize($handles);
6582

83+
$layout = $this->_view->getLayout();
84+
$this->layoutCacheKey->addCacheKeys($this->layoutCacheKeyName);
85+
6686
$this->_view->loadLayout($handles, true, true, false);
6787
$data = [];
6888

69-
$layout = $this->_view->getLayout();
7089
foreach ($blocks as $blockName) {
7190
$blockInstance = $layout->getBlock($blockName);
7291
if (is_object($blockInstance)) {

app/code/Magento/PageCache/Test/Unit/Controller/Block/EsiTest.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ class EsiTest extends \PHPUnit\Framework\TestCase
3939
*/
4040
protected $layoutMock;
4141

42+
/**
43+
* @var \Magento\Framework\View\Layout\LayoutCacheKeyInterface|\PHPUnit_Framework_MockObject_MockObject
44+
*/
45+
protected $layoutCacheKeyMock;
46+
4247
/**
4348
* @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Translate\InlineInterface
4449
*/
@@ -52,6 +57,8 @@ protected function setUp()
5257
$this->layoutMock = $this->getMockBuilder(\Magento\Framework\View\Layout::class)
5358
->disableOriginalConstructor()->getMock();
5459

60+
$this->layoutCacheKeyMock = $this->getMockForAbstractClass(\Magento\Framework\View\Layout\LayoutCacheKeyInterface::class);
61+
5562
$contextMock =
5663
$this->getMockBuilder(\Magento\Framework\App\Action\Context::class)
5764
->disableOriginalConstructor()->getMock();
@@ -76,7 +83,8 @@ protected function setUp()
7683
'context' => $contextMock,
7784
'translateInline' => $this->translateInline,
7885
'jsonSerializer' => new \Magento\Framework\Serialize\Serializer\Json(),
79-
'base64jsonSerializer' => new \Magento\Framework\Serialize\Serializer\Base64Json()
86+
'base64jsonSerializer' => new \Magento\Framework\Serialize\Serializer\Base64Json(),
87+
'layoutCacheKey' => $this->layoutCacheKeyMock
8088
]
8189
);
8290
}
@@ -104,6 +112,11 @@ public function testExecute($blockClass, $shouldSetHeaders)
104112

105113
$this->viewMock->expects($this->once())->method('getLayout')->will($this->returnValue($this->layoutMock));
106114

115+
$this->layoutMock->expects($this->never())
116+
->method('getUpdate');
117+
$this->layoutCacheKeyMock->expects($this->atLeastOnce())
118+
->method('addCacheKeys');
119+
107120
$this->layoutMock->expects($this->once())
108121
->method('getBlock')
109122
->with($this->equalTo($block))

app/code/Magento/PageCache/Test/Unit/Controller/Block/RenderTest.php

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,26 @@ class RenderTest extends \PHPUnit\Framework\TestCase
4444
*/
4545
protected $layoutMock;
4646

47+
/**
48+
* @var \Magento\Framework\View\Layout\ProcessorInterface|\PHPUnit_Framework_MockObject_MockObject
49+
*/
50+
protected $layoutProcessorMock;
51+
52+
/**
53+
* @var \Magento\Framework\View\Layout\LayoutCacheKeyInterface|\PHPUnit_Framework_MockObject_MockObject
54+
*/
55+
protected $layoutCacheKeyMock;
56+
4757
/**
4858
* Set up before test
4959
*/
5060
protected function setUp()
5161
{
52-
$this->layoutMock = $this->getMockBuilder(
53-
\Magento\Framework\View\Layout::class
54-
)->disableOriginalConstructor()->getMock();
62+
$this->layoutMock = $this->getMockBuilder(\Magento\Framework\View\Layout::class)
63+
->disableOriginalConstructor()->getMock();
64+
65+
$this->layoutProcessorMock = $this->getMockForAbstractClass(\Magento\Framework\View\Layout\ProcessorInterface::class);
66+
$this->layoutCacheKeyMock = $this->getMockForAbstractClass(\Magento\Framework\View\Layout\LayoutCacheKeyInterface::class);
5567

5668
$contextMock = $this->getMockBuilder(\Magento\Framework\App\Action\Context::class)
5769
->disableOriginalConstructor()->getMock();
@@ -65,6 +77,8 @@ protected function setUp()
6577
$this->viewMock = $this->getMockBuilder(\Magento\Framework\App\View::class)
6678
->disableOriginalConstructor()->getMock();
6779

80+
$this->layoutMock->expects($this->any())->method('getUpdate')->will($this->returnValue($this->layoutProcessorMock));
81+
6882
$contextMock->expects($this->any())->method('getRequest')->will($this->returnValue($this->requestMock));
6983
$contextMock->expects($this->any())->method('getResponse')->will($this->returnValue($this->responseMock));
7084
$contextMock->expects($this->any())->method('getView')->will($this->returnValue($this->viewMock));
@@ -78,7 +92,8 @@ protected function setUp()
7892
'context' => $contextMock,
7993
'translateInline' => $this->translateInline,
8094
'jsonSerializer' => new \Magento\Framework\Serialize\Serializer\Json(),
81-
'base64jsonSerializer' => new \Magento\Framework\Serialize\Serializer\Base64Json()
95+
'base64jsonSerializer' => new \Magento\Framework\Serialize\Serializer\Base64Json(),
96+
'layoutCacheKey' => $this->layoutCacheKeyMock
8297
]
8398
);
8499
}
@@ -88,6 +103,8 @@ public function testExecuteNotAjax()
88103
$this->requestMock->expects($this->once())->method('isAjax')->will($this->returnValue(false));
89104
$this->requestMock->expects($this->once())->method('setActionName')->will($this->returnValue('noroute'));
90105
$this->requestMock->expects($this->once())->method('setDispatched')->will($this->returnValue(false));
106+
$this->layoutCacheKeyMock->expects($this->never())
107+
->method('addCacheKeys');
91108
$this->action->execute();
92109
}
93110

@@ -105,6 +122,8 @@ public function testExecuteNoParams()
105122
->method('getParam')
106123
->with($this->equalTo('handles'), $this->equalTo(''))
107124
->will($this->returnValue(''));
125+
$this->layoutCacheKeyMock->expects($this->never())
126+
->method('addCacheKeys');
108127
$this->action->execute();
109128
}
110129

@@ -150,6 +169,10 @@ public function testExecute()
150169
->will($this->returnValue(base64_encode(json_encode($handles))));
151170
$this->viewMock->expects($this->once())->method('loadLayout')->with($this->equalTo($handles));
152171
$this->viewMock->expects($this->any())->method('getLayout')->will($this->returnValue($this->layoutMock));
172+
$this->layoutMock->expects($this->never())
173+
->method('getUpdate');
174+
$this->layoutCacheKeyMock->expects($this->atLeastOnce())
175+
->method('addCacheKeys');
153176
$this->layoutMock->expects($this->at(0))
154177
->method('getBlock')
155178
->with($this->equalTo($blocks[0]))

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
</argument>
3333
</arguments>
3434
</type>
35+
<type name="Magento\PageCache\Controller\Block">
36+
<arguments>
37+
<argument name="layoutCacheKey" xsi:type="object">Magento\Framework\View\Layout\LayoutCacheKeyInterface</argument>
38+
</arguments>
39+
</type>
3540
<preference for="Magento\PageCache\Model\VclGeneratorInterface" type="Magento\PageCache\Model\Varnish\VclGenerator"/>
3641
<preference for="Magento\PageCache\Model\VclTemplateLocatorInterface" type="Magento\PageCache\Model\Varnish\VclTemplateLocator"/>
3742
</config>

app/etc/di.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
<preference for="Magento\Framework\Event\ManagerInterface" type="Magento\Framework\Event\Manager\Proxy" />
6969
<preference for="Magento\Framework\View\LayoutInterface" type="Magento\Framework\View\Layout" />
7070
<preference for="Magento\Framework\View\Layout\ProcessorInterface" type="Magento\Framework\View\Model\Layout\Merge" />
71+
<preference for="Magento\Framework\View\Layout\LayoutCacheKeyInterface" type="Magento\Framework\View\Model\Layout\CacheKey" />
7172
<preference for="Magento\Framework\View\Url\ConfigInterface" type="Magento\Framework\View\Url\Config" />
7273
<preference for="Magento\Framework\App\Route\ConfigInterface" type="Magento\Framework\App\Route\Config" />
7374
<preference for="Magento\Framework\App\ResourceConnection\ConfigInterface" type="Magento\Framework\App\ResourceConnection\Config\Proxy" />
@@ -740,6 +741,7 @@
740741
<argument name="fileSource" xsi:type="object">Magento\Framework\View\Layout\File\Collector\Aggregated\Proxy</argument>
741742
<argument name="pageLayoutFileSource" xsi:type="object">pageLayoutFileCollectorAggregated</argument>
742743
<argument name="cache" xsi:type="object">Magento\Framework\App\Cache\Type\Layout</argument>
744+
<argument name="layoutCacheKey" xsi:type="object">Magento\Framework\View\Layout\LayoutCacheKeyInterface</argument>
743745
</arguments>
744746
</type>
745747
<type name="CSSmin">

dev/tests/integration/testsuite/Magento/Framework/View/Layout/MergeTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
use Magento\Framework\App\State;
99
use Magento\Framework\Phrase;
10+
use Magento\Framework\View\Layout\LayoutCacheKeyInterface;
1011

1112
/**
1213
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -65,6 +66,11 @@ class MergeTest extends \PHPUnit\Framework\TestCase
6566
*/
6667
protected $pageConfig;
6768

69+
/**
70+
* @var LayoutCacheKeyInterface|\PHPUnit_Framework_MockObject_MockObject
71+
*/
72+
protected $layoutCacheKeyMock;
73+
6874
protected function setUp()
6975
{
7076
$files = [];
@@ -119,6 +125,11 @@ function ($filename) use ($fileDriver) {
119125
)
120126
);
121127

128+
$this->layoutCacheKeyMock = $this->getMockForAbstractClass(LayoutCacheKeyInterface::class);
129+
$this->layoutCacheKeyMock->expects($this->any())
130+
->method('getCacheKeys')
131+
->willReturn([]);
132+
122133
$this->_model = $objectHelper->getObject(
123134
\Magento\Framework\View\Model\Layout\Merge::class,
124135
[
@@ -134,6 +145,7 @@ function ($filename) use ($fileDriver) {
134145
'logger' => $this->_logger,
135146
'readFactory' => $readFactory,
136147
'pageConfig' => $this->pageConfig,
148+
'layoutCacheKey' => $this->layoutCacheKeyMock,
137149
]
138150
);
139151
}

dev/tests/integration/testsuite/Magento/Framework/View/Model/Layout/MergeTest.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*/
66
namespace Magento\Framework\View\Model\Layout;
77

8+
use Magento\Framework\View\Layout\LayoutCacheKeyInterface;
9+
810
class MergeTest extends \PHPUnit\Framework\TestCase
911
{
1012
/**
@@ -18,6 +20,11 @@ class MergeTest extends \PHPUnit\Framework\TestCase
1820
*/
1921
protected $model;
2022

23+
/**
24+
* @var LayoutCacheKeyInterface|\PHPUnit_Framework_MockObject_MockObject
25+
*/
26+
protected $layoutCacheKeyMock;
27+
2128
protected function setUp()
2229
{
2330
$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
@@ -62,9 +69,17 @@ protected function setUp()
6269
$link2->setLayoutUpdateId($layoutUpdate2->getId());
6370
$link2->save();
6471

72+
$this->layoutCacheKeyMock = $this->getMockForAbstractClass(LayoutCacheKeyInterface::class);
73+
$this->layoutCacheKeyMock->expects($this->any())
74+
->method('getCacheKeys')
75+
->willReturn([]);
76+
6577
$this->model = $objectManager->create(
6678
\Magento\Framework\View\Model\Layout\Merge::class,
67-
['theme' => $theme]
79+
[
80+
'theme' => $theme,
81+
'layoutCacheKey' => $this->layoutCacheKeyMock,
82+
]
6883
);
6984
}
7085

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Framework\View\Layout;
7+
8+
/**
9+
* Interface LayoutCacheKeyInterface
10+
*/
11+
interface LayoutCacheKeyInterface
12+
{
13+
/**
14+
* Add cache key(s) for generating different cache id for same handles
15+
*
16+
* @param array|string $cacheKeys
17+
* @return void
18+
*/
19+
public function addCacheKeys($cacheKeys);
20+
21+
/**
22+
* Return cache keys array
23+
*
24+
* @return array
25+
*/
26+
public function getCacheKeys();
27+
}

lib/internal/Magento/Framework/View/Layout/ProcessorInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public function getFileLayoutUpdatesXml();
126126
public function getContainers();
127127

128128
/**
129-
* Return cache ID based current area/package/theme/store and handles
129+
* Return cache ID based current area/package/theme/store, handles and cache key(s)
130130
*
131131
* @return string
132132
*/
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\Framework\View\Model\Layout;
7+
8+
/**
9+
* Layout cache key model
10+
*/
11+
class CacheKey implements \Magento\Framework\View\Layout\LayoutCacheKeyInterface
12+
{
13+
/**
14+
* Cache keys to be able to generate different cache id for same handles
15+
*
16+
* @var array
17+
*/
18+
private $cacheKeys = [];
19+
20+
/**
21+
* Add cache key(s) for generating different cache id for same handles
22+
*
23+
* @param array|string $cacheKeys
24+
* @return void
25+
*/
26+
public function addCacheKeys($cacheKeys)
27+
{
28+
if (!is_array($cacheKeys)) {
29+
$cacheKeys = [$cacheKeys];
30+
}
31+
$this->cacheKeys = array_merge($this->cacheKeys, $cacheKeys);
32+
}
33+
34+
/**
35+
* Return cache keys array
36+
*
37+
* @return array
38+
*/
39+
public function getCacheKeys()
40+
{
41+
return $this->cacheKeys;
42+
}
43+
}

0 commit comments

Comments
 (0)