Browse Source

Fixes #15120: Refactored dynamic caching introducing `DynamicContentAwareInterface` and `DynamicContentAwareTrait`

tags/2.0.14
Sergey Makinen 7 years ago committed by Alexander Makarov
parent
commit
c382d3e8d4
No known key found for this signature in database
GPG Key ID: 3617B79C6A325E4A
  1. 3
      docs/guide/caching-fragment.md
  2. 1
      framework/CHANGELOG.md
  3. 40
      framework/base/DynamicContentAwareInterface.php
  4. 77
      framework/base/DynamicContentAwareTrait.php
  5. 71
      framework/base/View.php
  6. 71
      framework/filters/PageCache.php
  7. 48
      framework/widgets/FragmentCache.php

3
docs/guide/caching-fragment.md

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

1
framework/CHANGELOG.md

@ -4,6 +4,7 @@ Yii Framework 2 Change Log
2.0.14 under development
------------------------
- Enh #15120: Refactored dynamic caching introducing `DynamicContentAwareInterface` and `DynamicContentAwareTrait` (sergeymakinen)
- Bug #8983: Only truncate the original log file for rotation (matthewyang, developeruz)
- Bug #11401: Fixed `yii\web\DbSession` concurrency issues when writing and regenerating IDs (samdark, andreasanta, cebe)
- Bug #13034: Fixed `normalizePath` for windows network shares that start with two backslashes (developeruz)

40
framework/base/DynamicContentAwareInterface.php

@ -0,0 +1,40 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
/**
* DynamicContentAwareInterface is the interface that should be implemented by classes
* which support a [[View]] dynamic content feature.
*
* @author Sergey Makinen <sergey@makinen.ru>
* @since 2.0.14
*/
interface DynamicContentAwareInterface
{
/**
* Returns a list of placeholders for dynamic content. This method
* is used internally to implement the content caching feature.
* @return array a list of placeholders.
*/
public function getDynamicPlaceholders();
/**
* Sets a list of placeholders for dynamic content. This method
* is used internally to implement the content caching feature.
* @param array $placeholders a list of placeholders.
*/
public function setDynamicPlaceholders($placeholders);
/**
* Adds a placeholder for dynamic content.
* This method is used internally to implement the content caching feature.
* @param string $name the placeholder name.
* @param string $statements the PHP statements for generating the dynamic content.
*/
public function addDynamicPlaceholder($name, $statements);
}

77
framework/base/DynamicContentAwareTrait.php

@ -0,0 +1,77 @@
<?php
namespace yii\base;
/**
* DynamicContentAwareTrait implements common methods for classes
* which support a [[View]] dynamic content feature.
*
* @author Sergey Makinen <sergey@makinen.ru>
* @since 2.0.14
*/
trait DynamicContentAwareTrait
{
/**
* @var string[] a list of placeholders for dynamic content
*/
private $_dynamicPlaceholders;
/**
* Returns the view object that can be used to render views or view files using dynamic contents.
* @return View the view object that can be used to render views or view files.
*/
abstract protected function getView();
/**
* {@inheritdoc}
*/
public function getDynamicPlaceholders()
{
return $this->_dynamicPlaceholders;
}
/**
* {@inheritdoc}
*/
public function setDynamicPlaceholders($placeholders)
{
$this->_dynamicPlaceholders = $placeholders;
}
/**
* {@inheritdoc}
*/
public function addDynamicPlaceholder($name, $statements)
{
$this->_dynamicPlaceholders[$name] = $statements;
}
/**
* Replaces placeholders in $content with results of evaluated dynamic statements.
* @param string $content content to be parsed.
* @param string[] $placeholders placeholders and their values.
* @param bool $isRestoredFromCache whether content is going to be restored from cache.
* @return string final content.
*/
protected function updateDynamicContent($content, $placeholders, $isRestoredFromCache = false)
{
if (empty($placeholders) || !is_array($placeholders)) {
return $content;
}
if (count($this->getView()->getDynamicContents()) === 0) {
// outermost cache: replace placeholder with dynamic content
foreach ($placeholders as $name => $statements) {
$placeholders[$name] = $this->getView()->evaluateDynamicContent($statements);
}
$content = strtr($content, $placeholders);
}
if ($isRestoredFromCache) {
foreach ($placeholders as $name => $statements) {
$this->getView()->addDynamicPlaceholder($name, $statements);
}
}
return $content;
}
}

71
framework/base/View.php

@ -26,7 +26,7 @@ use yii\widgets\FragmentCache;
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class View extends Component
class View extends Component implements DynamicContentAwareInterface
{
/**
* @event Event an event that is triggered by [[beginPage()]].
@ -86,15 +86,19 @@ class View extends Component
*/
public $blocks;
/**
* @var array a list of currently active fragment cache widgets. This property
* is used internally to implement the content caching feature. Do not modify it directly.
* @var array|DynamicContentAwareInterface[] a list of currently active dynamic content class instances.
* This property is used internally to implement the dynamic content caching feature. Do not modify it directly.
* @internal
* @deprecated Sice 2.0.14. Do not use this property directly. Use methods [[getDynamicContents()]],
* [[pushDynamicContent()]], [[popDynamicContent()]] instead.
*/
public $cacheStack = [];
/**
* @var array a list of placeholders for embedding dynamic contents. This property
* is used internally to implement the content caching feature. Do not modify it directly.
* @internal
* @deprecated Since 2.0.14. Do not use this property directly. Use methods [[getDynamicPlaceholders()]],
* [[setDynamicPlaceholders()]], [[addDynamicPlaceholder()]] instead.
*/
public $dynamicPlaceholders = [];
@ -371,18 +375,36 @@ class View extends Component
}
/**
* Adds a placeholder for dynamic content.
* This method is internally used.
* @param string $placeholder the placeholder name
* @param string $statements the PHP statements for generating the dynamic content
* {@inheritdoc}
*/
public function getDynamicPlaceholders()
{
return $this->dynamicPlaceholders;
}
/**
* {@inheritdoc}
*/
public function setDynamicPlaceholders($placeholders)
{
$this->dynamicPlaceholders = $placeholders;
}
/**
* {@inheritdoc}
*/
public function addDynamicPlaceholder($placeholder, $statements)
{
foreach ($this->cacheStack as $cache) {
$cache->dynamicPlaceholders[$placeholder] = $statements;
if ($cache instanceof DynamicContentAwareInterface) {
$cache->addDynamicPlaceholder($placeholder, $statements);
} else {
// To be removed in 2.1
$cache->dynamicPlaceholders[$placeholder] = $statements;
}
}
$this->dynamicPlaceholders[$placeholder] = $statements;
}
}
/**
* Evaluates the given PHP statements.
@ -396,6 +418,37 @@ class View extends Component
}
/**
* Returns a list of currently active dynamic content class instances.
* @return DynamicContentAwareInterface[] class instances supporting dynamic contents.
* @since 2.0.14
*/
public function getDynamicContents()
{
return $this->cacheStack;
}
/**
* Adds a class instance supporting dynamic contents to the end of a list of currently active
* dynamic content class instances.
* @param DynamicContentAwareInterface $instance class instance supporting dynamic contents.
* @since 2.0.14
*/
public function pushDynamicContent(DynamicContentAwareInterface $instance)
{
$this->cacheStack[] = $instance;
}
/**
* Removes a last class instance supporting dynamic contents from a list of currently active
* dynamic content class instances.
* @since 2.0.14
*/
public function popDynamicContent()
{
array_pop($this->cacheStack);
}
/**
* Begins recording a block.
*
* This method is a shortcut to beginning [[Block]].

71
framework/filters/PageCache.php

@ -10,6 +10,8 @@ namespace yii\filters;
use Yii;
use yii\base\Action;
use yii\base\ActionFilter;
use yii\base\DynamicContentAwareInterface;
use yii\base\DynamicContentAwareTrait;
use yii\caching\CacheInterface;
use yii\caching\Dependency;
use yii\di\Instance;
@ -49,8 +51,16 @@ use yii\web\Response;
* @author Sergey Makinen <sergey@makinen.ru>
* @since 2.0
*/
class PageCache extends ActionFilter
class PageCache extends ActionFilter implements DynamicContentAwareInterface
{
use DynamicContentAwareTrait;
/**
* Page cache version, to detect incompatibilities in cached values when the
* data format of the cache changes.
*/
const PAGE_CACHE_VERSION = 1;
/**
* @var bool whether the content being cached should be differentiated according to the route.
* A route consists of the requested controller ID and action ID. Defaults to `true`.
@ -124,13 +134,6 @@ class PageCache extends ActionFilter
* @since 2.0.4
*/
public $cacheHeaders = true;
/**
* @var array a list of placeholders for embedding dynamic contents. This property
* is used internally to implement the content caching feature. Do not modify it.
* @internal
* @since 2.0.11
*/
public $dynamicPlaceholders;
/**
@ -164,8 +167,8 @@ class PageCache extends ActionFilter
$response = Yii::$app->getResponse();
$data = $this->cache->get($this->calculateCacheKey());
if (!is_array($data) || !isset($data['cacheVersion']) || $data['cacheVersion'] !== 1) {
$this->view->cacheStack[] = $this;
if (!is_array($data) || !isset($data['cacheVersion']) || $data['cacheVersion'] !== static::PAGE_CACHE_VERSION) {
$this->view->pushDynamicContent($this);
ob_start();
ob_implicit_flush(false);
$response->on(Response::EVENT_AFTER_SEND, [$this, 'cacheResponse']);
@ -217,13 +220,7 @@ class PageCache extends ActionFilter
}
}
if (!empty($data['dynamicPlaceholders']) && is_array($data['dynamicPlaceholders'])) {
if (empty($this->view->cacheStack)) {
// outermost cache: replace placeholder with dynamic content
$response->content = $this->updateDynamicContent($response->content, $data['dynamicPlaceholders']);
}
foreach ($data['dynamicPlaceholders'] as $name => $statements) {
$this->view->addDynamicPlaceholder($name, $statements);
}
$response->content = $this->updateDynamicContent($response->content, $data['dynamicPlaceholders'], true);
}
$this->afterRestoreResponse(isset($data['cacheData']) ? $data['cacheData'] : null);
}
@ -234,20 +231,16 @@ class PageCache extends ActionFilter
*/
public function cacheResponse()
{
array_pop($this->view->cacheStack);
$this->view->popDynamicContent();
$beforeCacheResponseResult = $this->beforeCacheResponse();
if ($beforeCacheResponseResult === false) {
$content = ob_get_clean();
if (empty($this->view->cacheStack) && !empty($this->dynamicPlaceholders)) {
$content = $this->updateDynamicContent($content, $this->dynamicPlaceholders);
}
echo $content;
echo $this->updateDynamicContent(ob_get_clean(), $this->getDynamicPlaceholders());
return;
}
$response = Yii::$app->getResponse();
$data = [
'cacheVersion' => 1,
'cacheVersion' => static::PAGE_CACHE_VERSION,
'cacheData' => is_array($beforeCacheResponseResult) ? $beforeCacheResponseResult : null,
'content' => ob_get_clean(),
];
@ -255,16 +248,14 @@ class PageCache extends ActionFilter
return;
}
$data['dynamicPlaceholders'] = $this->dynamicPlaceholders;
$data['dynamicPlaceholders'] = $this->getDynamicPlaceholders();
foreach (['format', 'version', 'statusCode', 'statusText'] as $name) {
$data[$name] = $response->{$name};
}
$this->insertResponseCollectionIntoData($response, 'headers', $data);
$this->insertResponseCollectionIntoData($response, 'cookies', $data);
$this->cache->set($this->calculateCacheKey(), $data, $this->duration, $this->dependency);
if (empty($this->view->cacheStack) && !empty($this->dynamicPlaceholders)) {
$data['content'] = $this->updateDynamicContent($data['content'], $this->dynamicPlaceholders);
}
$data['content'] = $this->updateDynamicContent($data['content'], $this->getDynamicPlaceholders());
echo $data['content'];
}
@ -298,22 +289,6 @@ class PageCache extends ActionFilter
}
/**
* Replaces placeholders in content by results of evaluated dynamic statements.
* @param string $content content to be parsed.
* @param array $placeholders placeholders and their values.
* @return string final content.
* @since 2.0.11
*/
protected function updateDynamicContent($content, $placeholders)
{
foreach ($placeholders as $name => $statements) {
$placeholders[$name] = $this->view->evaluateDynamicContent($statements);
}
return strtr($content, $placeholders);
}
/**
* @return array the key used to cache response properties.
* @since 2.0.3
*/
@ -325,4 +300,12 @@ class PageCache extends ActionFilter
}
return array_merge($key, (array)$this->variations);
}
/**
* {@inheritdoc}
*/
protected function getView()
{
return $this->view;
}
}

48
framework/widgets/FragmentCache.php

@ -8,6 +8,8 @@
namespace yii\widgets;
use Yii;
use yii\base\DynamicContentAwareInterface;
use yii\base\DynamicContentAwareTrait;
use yii\base\Widget;
use yii\caching\CacheInterface;
use yii\caching\Dependency;
@ -22,8 +24,10 @@ use yii\di\Instance;
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class FragmentCache extends Widget
class FragmentCache extends Widget implements DynamicContentAwareInterface
{
use DynamicContentAwareTrait;
/**
* @var CacheInterface|array|string the cache object or the application component ID of the cache object.
* After the FragmentCache object is created, if you want to change this property,
@ -70,11 +74,6 @@ class FragmentCache extends Widget
* the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests).
*/
public $enabled = true;
/**
* @var array a list of placeholders for embedding dynamic contents. This property
* is used internally to implement the content caching feature. Do not modify it.
*/
public $dynamicPlaceholders;
/**
@ -87,7 +86,7 @@ class FragmentCache extends Widget
$this->cache = $this->enabled ? Instance::ensure($this->cache, 'yii\caching\CacheInterface') : null;
if ($this->cache instanceof CacheInterface && $this->getCachedContent() === false) {
$this->getView()->cacheStack[] = $this;
$this->getView()->pushDynamicContent($this);
ob_start();
ob_implicit_flush(false);
}
@ -104,7 +103,7 @@ class FragmentCache extends Widget
if (($content = $this->getCachedContent()) !== false) {
echo $content;
} elseif ($this->cache instanceof CacheInterface) {
array_pop($this->getView()->cacheStack);
$this->getView()->popDynamicContent();
$content = ob_get_clean();
if ($content === false || $content === '') {
@ -113,13 +112,9 @@ class FragmentCache extends Widget
if (is_array($this->dependency)) {
$this->dependency = Yii::createObject($this->dependency);
}
$data = [$content, $this->dynamicPlaceholders];
$data = [$content, $this->getDynamicPlaceholders()];
$this->cache->set($this->calculateKey(), $data, $this->duration, $this->dependency);
if (empty($this->getView()->cacheStack) && !empty($this->dynamicPlaceholders)) {
$content = $this->updateDynamicContent($content, $this->dynamicPlaceholders);
}
echo $content;
echo $this->updateDynamicContent($content, $this->getDynamicPlaceholders());
}
}
@ -155,34 +150,11 @@ class FragmentCache extends Widget
return $this->_content;
}
if (empty($this->getView()->cacheStack)) {
// outermost cache: replace placeholder with dynamic content
$this->_content = $this->updateDynamicContent($this->_content, $placeholders);
}
foreach ($placeholders as $name => $statements) {
$this->getView()->addDynamicPlaceholder($name, $statements);
}
$this->_content = $this->updateDynamicContent($this->_content, $placeholders, true);
return $this->_content;
}
/**
* Replaces placeholders in content by results of evaluated dynamic statements.
*
* @param string $content
* @param array $placeholders
* @return string final content
*/
protected function updateDynamicContent($content, $placeholders)
{
foreach ($placeholders as $name => $statements) {
$placeholders[$name] = $this->getView()->evaluateDynamicContent($statements);
}
return strtr($content, $placeholders);
}
/**
* Generates a unique key used for storing the content in cache.
* The key generated depends on both [[id]] and [[variations]].
* @return mixed a valid cache key

Loading…
Cancel
Save