diff --git a/framework/YiiBase.php b/framework/YiiBase.php index 678856c..16e237d 100644 --- a/framework/YiiBase.php +++ b/framework/YiiBase.php @@ -298,7 +298,7 @@ class YiiBase } } - if (isset($classFile, $alias)) { + if (isset($classFile, $alias) && is_file($classFile)) { if (!YII_DEBUG || basename(realpath($classFile)) === basename($alias) . '.php') { include($classFile); return true; diff --git a/framework/base/Component.php b/framework/base/Component.php index 515f7e1..2d081d3 100644 --- a/framework/base/Component.php +++ b/framework/base/Component.php @@ -58,7 +58,7 @@ class Component extends \yii\base\Object } } } - throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '.' . $name); + throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name); } /** @@ -105,9 +105,9 @@ class Component extends \yii\base\Object } } if (method_exists($this, 'get' . $name)) { - throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '.' . $name); + throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name); } else { - throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '.' . $name); + throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name); } } diff --git a/framework/base/Object.php b/framework/base/Object.php index f589e38..3bd8378 100644 --- a/framework/base/Object.php +++ b/framework/base/Object.php @@ -65,7 +65,7 @@ class Object if (method_exists($this, $getter)) { return $this->$getter(); } else { - throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '.' . $name); + throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name); } } @@ -86,9 +86,9 @@ class Object if (method_exists($this, $setter)) { $this->$setter($value); } elseif (method_exists($this, 'get' . $name)) { - throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '.' . $name); + throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name); } else { - throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '.' . $name); + throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name); } } @@ -129,7 +129,7 @@ class Object if (method_exists($this, $setter)) { $this->$setter(null); } elseif (method_exists($this, 'get' . $name)) { - throw new InvalidCallException('Unsetting read-only property: ' . get_class($this) . '.' . $name); + throw new InvalidCallException('Unsetting read-only property: ' . get_class($this) . '::' . $name); } } diff --git a/framework/base/View.php b/framework/base/View.php index 7170251..c7087c1 100644 --- a/framework/base/View.php +++ b/framework/base/View.php @@ -45,11 +45,21 @@ class View extends Component * through this property. */ public $clips; - /** - * @var Widget[] the widgets that are currently not ended + * @var Widget[] the widgets that are currently being rendered (not ended). This property + * is maintained by [[beginWidget()]] and [[endWidget()]] methods. Do not modify it directly. + */ + public $widgetStack = array(); + /** + * @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. + */ + public $cacheStack = array(); + /** + * @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. */ - private $_widgetStack = array(); + public $dynamicPlaceholders = array(); /** @@ -156,20 +166,47 @@ class View extends Component return ob_get_clean(); } + /** + * Renders dynamic content returned by the given PHP statements. + * This method is mainly used together with content caching (fragment caching and page caching) + * when some portions of the content (called *dynamic content*) should not be cached. + * The dynamic content must be returned by some PHP statements. + * @param string $statements the PHP statements for generating the dynamic content. + * @return string the placeholder of the dynamic content, or the dynamic content if there is no + * active content cache currently. + */ public function renderDynamic($statements) { - if (!empty($this->cachingStack)) { - $n = count($this->_dynamicOutput); + if (!empty($this->cacheStack)) { + $n = count($this->dynamicPlaceholders); $placeholder = ""; - foreach ($this->cachingStack as $cache) { - $cache->dynamicPlaceholders[$placeholder] = $statements; - } + $this->addDynamicPlaceholder($placeholder, $statements); return $placeholder; } else { return $this->evaluateDynamicContent($statements); } } + /** + * 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 + */ + public function addDynamicPlaceholder($placeholder, $statements) + { + foreach ($this->cacheStack as $cache) { + $cache->dynamicPlaceholders[$placeholder] = $statements; + } + $this->dynamicPlaceholders[$placeholder] = $statements; + } + + /** + * Evaluates the given PHP statements. + * This method is mainly used internally to implement dynamic content feature. + * @param string $statements the PHP statements to be evaluated. + * @return mixed the return value of the PHP statements. + */ public function evaluateDynamicContent($statements) { return eval($statements); @@ -267,7 +304,7 @@ class View extends Component public function beginWidget($class, $properties = array()) { $widget = $this->createWidget($class, $properties); - $this->_widgetStack[] = $widget; + $this->widgetStack[] = $widget; return $widget; } @@ -281,7 +318,7 @@ class View extends Component */ public function endWidget() { - $widget = array_pop($this->_widgetStack); + $widget = array_pop($this->widgetStack); if ($widget instanceof Widget) { $widget->run(); return $widget; @@ -363,8 +400,9 @@ class View extends Component public function beginCache($id, $properties = array()) { $properties['id'] = $id; + $properties['view'] = $this; /** @var $cache \yii\widgets\FragmentCache */ - $cache = $this->beginWidget('yii\widgets\OutputCache', $properties); + $cache = $this->beginWidget('yii\widgets\FragmentCache', $properties); if ($cache->getCachedContent() !== false) { $this->endCache(); return false; diff --git a/framework/widgets/FragmentCache.php b/framework/widgets/FragmentCache.php index b56b86f..3bb321e 100644 --- a/framework/widgets/FragmentCache.php +++ b/framework/widgets/FragmentCache.php @@ -67,7 +67,8 @@ class FragmentCache extends Widget */ public $view; /** - * @var array + * @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; @@ -83,8 +84,8 @@ class FragmentCache extends Widget if ($this->view === null) { $this->view = Yii::$app->getView(); } - if ($this->getCachedContent() === false && $this->getCache() !== null) { - array_push($this->view->cachingStack, $this); + if ($this->getCache() !== null && $this->getCachedContent() === false) { + $this->view->cacheStack[] = $this; ob_start(); ob_implicit_flush(false); } @@ -100,14 +101,18 @@ class FragmentCache extends Widget { if (($content = $this->getCachedContent()) !== false) { echo $content; - } elseif (($cache = $this->getCache()) !== false) { + } elseif (($cache = $this->getCache()) !== null) { $content = ob_get_clean(); - array_pop($this->view->cachingStack); + array_pop($this->view->cacheStack); if (is_array($this->dependency)) { $this->dependency = Yii::createObject($this->dependency); } $data = array($content, $this->dynamicPlaceholders); $cache->set($this->calculateKey(), $data, $this->duration, $this->dependency); + + if ($this->view->cacheStack === array() && !empty($this->dynamicPlaceholders)) { + $content = $this->updateDynamicContent($content, $this->dynamicPlaceholders); + } echo $content; } } @@ -131,10 +136,13 @@ class FragmentCache extends Widget if (is_array($data) && count($data) === 2) { list ($content, $placeholders) = $data; if (is_array($placeholders) && count($placeholders) > 0) { + if ($this->view->cacheStack === array()) { + // outermost cache: replace placeholder with dynamic content + $content = $this->updateDynamicContent($content, $placeholders); + } foreach ($placeholders as $name => $statements) { - $placeholders[$name] = $this->view->evaluateDynamicContent($statements); + $this->view->addDynamicPlaceholder($name, $statements); } - $content = strtr($content, $placeholders); } $this->_content = $content; } @@ -143,6 +151,14 @@ class FragmentCache extends Widget return $this->_content; } + protected function updateDynamicContent($content, $placeholders) + { + foreach ($placeholders as $name => $statements) { + $placeholders[$name] = $this->view->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]].