mirror of
https://github.com/yiisoft/yii2.git
synced 2025-08-26 14:26:54 +08:00
Fixes #15120: Refactored dynamic caching introducing DynamicContentAwareInterface
and DynamicContentAwareTrait
This commit is contained in:

committed by
Alexander Makarov

parent
1cd4bb8ac8
commit
c382d3e8d4
@ -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.
|
||||
|
@ -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
Normal file
40
framework/base/DynamicContentAwareInterface.php
Normal file
@ -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
Normal file
77
framework/base/DynamicContentAwareTrait.php
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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,16 +375,34 @@ 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) {
|
||||
if ($cache instanceof DynamicContentAwareInterface) {
|
||||
$cache->addDynamicPlaceholder($placeholder, $statements);
|
||||
} else {
|
||||
// To be removed in 2.1
|
||||
$cache->dynamicPlaceholders[$placeholder] = $statements;
|
||||
}
|
||||
}
|
||||
$this->dynamicPlaceholders[$placeholder] = $statements;
|
||||
}
|
||||
|
||||
@ -395,6 +417,37 @@ class View extends Component
|
||||
return eval($statements);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -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'];
|
||||
}
|
||||
|
||||
@ -297,22 +288,6 @@ class PageCache extends ActionFilter
|
||||
$data[$collectionName] = $all;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
@ -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,33 +150,10 @@ 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]].
|
||||
|
Reference in New Issue
Block a user