I wanted a simple way to use any cache from within a widget, possibly even a cache that is privately constructed and owned by the widget itself. Sometimes you need a cache that is specific to a widget, such as a menu - having a general cache for all widgets (and possibly other fragments/pags) may not be a good approach, since flushing that cache would cause all manner of cached content to be flushed, even if it wasn’t invalidated.
My approach is a behavior, that I can attach to any CCache instance, which allows me to simply begin() and end() a block of content - it supports default settings for expiration and dependency, so you can configure defaults for these in advanced, or override them when you call begin().
Here’s the code:
<?php
/**
* The behavior extends any of the CCache subclasses with begin() and end() methods,
* allowing for quick, simple caching in widgets, views or actions.
*
* You are responsible for introducing variability, e.g. appending variables to the
* cache $id for uniqueness.
*
* Example configuration:
*
* <code>
* 'components'=>array(
* ...
* 'widgetCache'=>array(
* 'class'=>'CDummyCache',
* 'behaviors'=>array('output'=>'GCacheOutputBehavior'),
* 'cachePath'=>APP_PATH.DIRECTORY_SEPARATOR.'runtime'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'widgets',
* ),
* ),
* </code>
*
* Example Widget using the widgetCache configured in the example above:
*
* <code>
* class Menu extends CWidget
* {
* public $active=null;
*
* public function run()
* {
* if (Yii::app()->widgetCache->begin(__CLASS__.'.'.$this->active))
* {
* $this->render('menu', array(
* ...
* ));
* Yii::app()->widgetCache->end();
* }
* }
* }
* <code>
*
*/
class GCacheOutputBehavior extends CBehavior
{
protected $_stack=array();
/**
* Default $expire setting for the begin() method
*/
public $expire=0;
/**
* Default $dependency setting for the begin() method
*/
public $dependency=null;
/**
* Begins caching. This method will display cached content if it is availabe. If not,
* it will start caching and would expect a call to end(), to save the content into cache.
*
* @return boolean returns true if your code needs to render the cached content, or
* false if the cached content was already available to output.
*/
public function begin($id, $expire=null, $dependency=null)
{
if ($this->getOwner()->offsetExists($id))
{
echo $this->getOwner()->get($id);
return false;
}
$this->_stack[] = array(
$id,
null,
$expire===null ? $this->expire : $expire,
$dependency===null ? $this->dependency : $dependency,
);
ob_start();
ob_implicit_flush(false);
return true;
}
/**
* Ends caching.
*/
public function end()
{
if (count($this->_stack)==0)
throw new CException(__CLASS__."::end() : end() without matching begin()");
$params = array_pop($this->_stack);
$params[1] = ob_get_flush();
call_user_func_array(array($this->getOwner(), 'set'), $params);
}
}