Skip to content

Commit c382d3e

Browse files
sergeymakinensamdark
authored andcommitted
Fixes yiisoft#15120: Refactored dynamic caching introducing DynamicContentAwareInterface and DynamicContentAwareTrait
1 parent 1cd4bb8 commit c382d3e

File tree

7 files changed

+220
-91
lines changed

7 files changed

+220
-91
lines changed

docs/guide/caching-fragment.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,6 @@ if ($this->beginCache($id1)) {
174174
The [[yii\base\View::renderDynamic()|renderDynamic()]] method takes a piece of PHP code as its parameter.
175175
The return value of the PHP code is treated as the dynamic content. The same PHP code will be executed
176176
for every request, no matter the enclosing fragment is being served from cached or not.
177+
178+
> Note: since version 2.0.14 a dynamic content API is exposed via the [[yii\base\DynamicContentAwareInterface]] interface and its [[yii\base\DynamicContentAwareTrait]] trait.
179+
As an example, you may refer to the [[yii\widgets\FragmentCache]] class.

framework/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Yii Framework 2 Change Log
44
2.0.14 under development
55
------------------------
66

7+
- Enh #15120: Refactored dynamic caching introducing `DynamicContentAwareInterface` and `DynamicContentAwareTrait` (sergeymakinen)
78
- Bug #8983: Only truncate the original log file for rotation (matthewyang, developeruz)
89
- Bug #11401: Fixed `yii\web\DbSession` concurrency issues when writing and regenerating IDs (samdark, andreasanta, cebe)
910
- Bug #13034: Fixed `normalizePath` for windows network shares that start with two backslashes (developeruz)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
/**
3+
* @link http://www.yiiframework.com/
4+
* @copyright Copyright (c) 2008 Yii Software LLC
5+
* @license http://www.yiiframework.com/license/
6+
*/
7+
8+
namespace yii\base;
9+
10+
/**
11+
* DynamicContentAwareInterface is the interface that should be implemented by classes
12+
* which support a [[View]] dynamic content feature.
13+
*
14+
* @author Sergey Makinen <[email protected]>
15+
* @since 2.0.14
16+
*/
17+
interface DynamicContentAwareInterface
18+
{
19+
/**
20+
* Returns a list of placeholders for dynamic content. This method
21+
* is used internally to implement the content caching feature.
22+
* @return array a list of placeholders.
23+
*/
24+
public function getDynamicPlaceholders();
25+
26+
/**
27+
* Sets a list of placeholders for dynamic content. This method
28+
* is used internally to implement the content caching feature.
29+
* @param array $placeholders a list of placeholders.
30+
*/
31+
public function setDynamicPlaceholders($placeholders);
32+
33+
/**
34+
* Adds a placeholder for dynamic content.
35+
* This method is used internally to implement the content caching feature.
36+
* @param string $name the placeholder name.
37+
* @param string $statements the PHP statements for generating the dynamic content.
38+
*/
39+
public function addDynamicPlaceholder($name, $statements);
40+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
namespace yii\base;
4+
5+
/**
6+
* DynamicContentAwareTrait implements common methods for classes
7+
* which support a [[View]] dynamic content feature.
8+
*
9+
* @author Sergey Makinen <[email protected]>
10+
* @since 2.0.14
11+
*/
12+
trait DynamicContentAwareTrait
13+
{
14+
/**
15+
* @var string[] a list of placeholders for dynamic content
16+
*/
17+
private $_dynamicPlaceholders;
18+
19+
/**
20+
* Returns the view object that can be used to render views or view files using dynamic contents.
21+
* @return View the view object that can be used to render views or view files.
22+
*/
23+
abstract protected function getView();
24+
25+
/**
26+
* {@inheritdoc}
27+
*/
28+
public function getDynamicPlaceholders()
29+
{
30+
return $this->_dynamicPlaceholders;
31+
}
32+
33+
/**
34+
* {@inheritdoc}
35+
*/
36+
public function setDynamicPlaceholders($placeholders)
37+
{
38+
$this->_dynamicPlaceholders = $placeholders;
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public function addDynamicPlaceholder($name, $statements)
45+
{
46+
$this->_dynamicPlaceholders[$name] = $statements;
47+
}
48+
49+
/**
50+
* Replaces placeholders in $content with results of evaluated dynamic statements.
51+
* @param string $content content to be parsed.
52+
* @param string[] $placeholders placeholders and their values.
53+
* @param bool $isRestoredFromCache whether content is going to be restored from cache.
54+
* @return string final content.
55+
*/
56+
protected function updateDynamicContent($content, $placeholders, $isRestoredFromCache = false)
57+
{
58+
if (empty($placeholders) || !is_array($placeholders)) {
59+
return $content;
60+
}
61+
62+
if (count($this->getView()->getDynamicContents()) === 0) {
63+
// outermost cache: replace placeholder with dynamic content
64+
foreach ($placeholders as $name => $statements) {
65+
$placeholders[$name] = $this->getView()->evaluateDynamicContent($statements);
66+
}
67+
$content = strtr($content, $placeholders);
68+
}
69+
if ($isRestoredFromCache) {
70+
foreach ($placeholders as $name => $statements) {
71+
$this->getView()->addDynamicPlaceholder($name, $statements);
72+
}
73+
}
74+
75+
return $content;
76+
}
77+
}

framework/base/View.php

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* @author Qiang Xue <[email protected]>
2727
* @since 2.0
2828
*/
29-
class View extends Component
29+
class View extends Component implements DynamicContentAwareInterface
3030
{
3131
/**
3232
* @event Event an event that is triggered by [[beginPage()]].
@@ -86,15 +86,19 @@ class View extends Component
8686
*/
8787
public $blocks;
8888
/**
89-
* @var array a list of currently active fragment cache widgets. This property
90-
* is used internally to implement the content caching feature. Do not modify it directly.
89+
* @var array|DynamicContentAwareInterface[] a list of currently active dynamic content class instances.
90+
* This property is used internally to implement the dynamic content caching feature. Do not modify it directly.
9191
* @internal
92+
* @deprecated Sice 2.0.14. Do not use this property directly. Use methods [[getDynamicContents()]],
93+
* [[pushDynamicContent()]], [[popDynamicContent()]] instead.
9294
*/
9395
public $cacheStack = [];
9496
/**
9597
* @var array a list of placeholders for embedding dynamic contents. This property
9698
* is used internally to implement the content caching feature. Do not modify it directly.
9799
* @internal
100+
* @deprecated Since 2.0.14. Do not use this property directly. Use methods [[getDynamicPlaceholders()]],
101+
* [[setDynamicPlaceholders()]], [[addDynamicPlaceholder()]] instead.
98102
*/
99103
public $dynamicPlaceholders = [];
100104

@@ -371,18 +375,36 @@ public function renderDynamic($statements)
371375
}
372376

373377
/**
374-
* Adds a placeholder for dynamic content.
375-
* This method is internally used.
376-
* @param string $placeholder the placeholder name
377-
* @param string $statements the PHP statements for generating the dynamic content
378+
* {@inheritdoc}
379+
*/
380+
public function getDynamicPlaceholders()
381+
{
382+
return $this->dynamicPlaceholders;
383+
}
384+
385+
/**
386+
* {@inheritdoc}
387+
*/
388+
public function setDynamicPlaceholders($placeholders)
389+
{
390+
$this->dynamicPlaceholders = $placeholders;
391+
}
392+
393+
/**
394+
* {@inheritdoc}
378395
*/
379396
public function addDynamicPlaceholder($placeholder, $statements)
380397
{
381398
foreach ($this->cacheStack as $cache) {
382-
$cache->dynamicPlaceholders[$placeholder] = $statements;
399+
if ($cache instanceof DynamicContentAwareInterface) {
400+
$cache->addDynamicPlaceholder($placeholder, $statements);
401+
} else {
402+
// To be removed in 2.1
403+
$cache->dynamicPlaceholders[$placeholder] = $statements;
404+
}
383405
}
384406
$this->dynamicPlaceholders[$placeholder] = $statements;
385-
}
407+
}
386408

387409
/**
388410
* Evaluates the given PHP statements.
@@ -395,6 +417,37 @@ public function evaluateDynamicContent($statements)
395417
return eval($statements);
396418
}
397419

420+
/**
421+
* Returns a list of currently active dynamic content class instances.
422+
* @return DynamicContentAwareInterface[] class instances supporting dynamic contents.
423+
* @since 2.0.14
424+
*/
425+
public function getDynamicContents()
426+
{
427+
return $this->cacheStack;
428+
}
429+
430+
/**
431+
* Adds a class instance supporting dynamic contents to the end of a list of currently active
432+
* dynamic content class instances.
433+
* @param DynamicContentAwareInterface $instance class instance supporting dynamic contents.
434+
* @since 2.0.14
435+
*/
436+
public function pushDynamicContent(DynamicContentAwareInterface $instance)
437+
{
438+
$this->cacheStack[] = $instance;
439+
}
440+
441+
/**
442+
* Removes a last class instance supporting dynamic contents from a list of currently active
443+
* dynamic content class instances.
444+
* @since 2.0.14
445+
*/
446+
public function popDynamicContent()
447+
{
448+
array_pop($this->cacheStack);
449+
}
450+
398451
/**
399452
* Begins recording a block.
400453
*

framework/filters/PageCache.php

Lines changed: 27 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
use Yii;
1111
use yii\base\Action;
1212
use yii\base\ActionFilter;
13+
use yii\base\DynamicContentAwareInterface;
14+
use yii\base\DynamicContentAwareTrait;
1315
use yii\caching\CacheInterface;
1416
use yii\caching\Dependency;
1517
use yii\di\Instance;
@@ -49,8 +51,16 @@
4951
* @author Sergey Makinen <[email protected]>
5052
* @since 2.0
5153
*/
52-
class PageCache extends ActionFilter
54+
class PageCache extends ActionFilter implements DynamicContentAwareInterface
5355
{
56+
use DynamicContentAwareTrait;
57+
58+
/**
59+
* Page cache version, to detect incompatibilities in cached values when the
60+
* data format of the cache changes.
61+
*/
62+
const PAGE_CACHE_VERSION = 1;
63+
5464
/**
5565
* @var bool whether the content being cached should be differentiated according to the route.
5666
* A route consists of the requested controller ID and action ID. Defaults to `true`.
@@ -124,13 +134,6 @@ class PageCache extends ActionFilter
124134
* @since 2.0.4
125135
*/
126136
public $cacheHeaders = true;
127-
/**
128-
* @var array a list of placeholders for embedding dynamic contents. This property
129-
* is used internally to implement the content caching feature. Do not modify it.
130-
* @internal
131-
* @since 2.0.11
132-
*/
133-
public $dynamicPlaceholders;
134137

135138

136139
/**
@@ -164,8 +167,8 @@ public function beforeAction($action)
164167

165168
$response = Yii::$app->getResponse();
166169
$data = $this->cache->get($this->calculateCacheKey());
167-
if (!is_array($data) || !isset($data['cacheVersion']) || $data['cacheVersion'] !== 1) {
168-
$this->view->cacheStack[] = $this;
170+
if (!is_array($data) || !isset($data['cacheVersion']) || $data['cacheVersion'] !== static::PAGE_CACHE_VERSION) {
171+
$this->view->pushDynamicContent($this);
169172
ob_start();
170173
ob_implicit_flush(false);
171174
$response->on(Response::EVENT_AFTER_SEND, [$this, 'cacheResponse']);
@@ -217,13 +220,7 @@ protected function restoreResponse($response, $data)
217220
}
218221
}
219222
if (!empty($data['dynamicPlaceholders']) && is_array($data['dynamicPlaceholders'])) {
220-
if (empty($this->view->cacheStack)) {
221-
// outermost cache: replace placeholder with dynamic content
222-
$response->content = $this->updateDynamicContent($response->content, $data['dynamicPlaceholders']);
223-
}
224-
foreach ($data['dynamicPlaceholders'] as $name => $statements) {
225-
$this->view->addDynamicPlaceholder($name, $statements);
226-
}
223+
$response->content = $this->updateDynamicContent($response->content, $data['dynamicPlaceholders'], true);
227224
}
228225
$this->afterRestoreResponse(isset($data['cacheData']) ? $data['cacheData'] : null);
229226
}
@@ -234,37 +231,31 @@ protected function restoreResponse($response, $data)
234231
*/
235232
public function cacheResponse()
236233
{
237-
array_pop($this->view->cacheStack);
234+
$this->view->popDynamicContent();
238235
$beforeCacheResponseResult = $this->beforeCacheResponse();
239236
if ($beforeCacheResponseResult === false) {
240-
$content = ob_get_clean();
241-
if (empty($this->view->cacheStack) && !empty($this->dynamicPlaceholders)) {
242-
$content = $this->updateDynamicContent($content, $this->dynamicPlaceholders);
243-
}
244-
echo $content;
237+
echo $this->updateDynamicContent(ob_get_clean(), $this->getDynamicPlaceholders());
245238
return;
246239
}
247240

248241
$response = Yii::$app->getResponse();
249242
$data = [
250-
'cacheVersion' => 1,
243+
'cacheVersion' => static::PAGE_CACHE_VERSION,
251244
'cacheData' => is_array($beforeCacheResponseResult) ? $beforeCacheResponseResult : null,
252245
'content' => ob_get_clean(),
253246
];
254247
if ($data['content'] === false || $data['content'] === '') {
255248
return;
256249
}
257250

258-
$data['dynamicPlaceholders'] = $this->dynamicPlaceholders;
251+
$data['dynamicPlaceholders'] = $this->getDynamicPlaceholders();
259252
foreach (['format', 'version', 'statusCode', 'statusText'] as $name) {
260253
$data[$name] = $response->{$name};
261254
}
262255
$this->insertResponseCollectionIntoData($response, 'headers', $data);
263256
$this->insertResponseCollectionIntoData($response, 'cookies', $data);
264257
$this->cache->set($this->calculateCacheKey(), $data, $this->duration, $this->dependency);
265-
if (empty($this->view->cacheStack) && !empty($this->dynamicPlaceholders)) {
266-
$data['content'] = $this->updateDynamicContent($data['content'], $this->dynamicPlaceholders);
267-
}
258+
$data['content'] = $this->updateDynamicContent($data['content'], $this->getDynamicPlaceholders());
268259
echo $data['content'];
269260
}
270261

@@ -297,22 +288,6 @@ private function insertResponseCollectionIntoData(Response $response, $collectio
297288
$data[$collectionName] = $all;
298289
}
299290

300-
/**
301-
* Replaces placeholders in content by results of evaluated dynamic statements.
302-
* @param string $content content to be parsed.
303-
* @param array $placeholders placeholders and their values.
304-
* @return string final content.
305-
* @since 2.0.11
306-
*/
307-
protected function updateDynamicContent($content, $placeholders)
308-
{
309-
foreach ($placeholders as $name => $statements) {
310-
$placeholders[$name] = $this->view->evaluateDynamicContent($statements);
311-
}
312-
313-
return strtr($content, $placeholders);
314-
}
315-
316291
/**
317292
* @return array the key used to cache response properties.
318293
* @since 2.0.3
@@ -325,4 +300,12 @@ protected function calculateCacheKey()
325300
}
326301
return array_merge($key, (array)$this->variations);
327302
}
303+
304+
/**
305+
* {@inheritdoc}
306+
*/
307+
protected function getView()
308+
{
309+
return $this->view;
310+
}
328311
}

0 commit comments

Comments
 (0)