Chain of caching

I’ve searched this forum but not found any topic about chain of cache.

So I write this post, hope this will help someone, and please let me know if there has any problem in code.

I face a scenerio when I need caching in chain like this:

$_GLOBAL(cache in one request) -> MEMCACHE -> DBCACHE -> FILECACHE …

So, this is the code:


<?php


/**

 * Chain of responsibility pattern

 * Configure in main.php like these:

 * 

 *              'cache'=>array(

 *                   'class'=>'application.components.ChainCache',

 *                   'chainCache'=>array(

 *                       array(

 *                           'class'=>'GlobalCache', //cache for a request (a static $variable)

 *                       ),

 *                       array(

 *                               'class'=>'CDbCache',

 *                               'autoCreateCacheTable' => true,

 *                       ),

 *                   ),

 *               ), 

 */

class ChainCache extends CCache {


    /**

     * Array of Cache classes configured in main.php

     * @var array 

     */

    public $chainCache = array();


    /**

     * Default expire time of a cache when it's auto set a value

     * @var integer 

     */

    public $defaultExpireTime = 300;


    /**

     * Array of cache components, corresponding to $chainCache config

     * @var array 

     */

    private $caches = array();


    /**

     * Lowest level cache.

     * @var CCache

     */

    private $cache;


    public function init() {

        parent::init();


        if (empty($this->chainCache)) {

            throw new Exception('No cache class declared in [chainCache] configuration');

        }


        $c = count($this->chainCache);

        

        for ($i = 0; $i < $c; $i++) {

            $this->caches[$i] = Yii::createComponent($this->chainCache[$i]);

            $this->caches[$i]->init();


            $behavior = new ChainCacheBehavior();

            $this->caches[$i]->attachBehavior('nextCache', $behavior);

        }


        for ($i = 0; $i < $c - 1; $i++) {

            $this->caches[$i]->assignNext($this->caches[$i + 1]);

        }


        $this->cache = $this->caches[0];

    }


    protected function getValue($key) {

        $val = $this->cache->getValue($key);


        if ($val == false) {

            $cache = $this->cache;

            $previous = $cache;


            while (($cache = $cache->next) != null) {

                $val = $cache->getValue($key);


                if ($val !== false) {

                    $previous->setValue($key, $val, $this->defaultExpireTime);

                    return $val;

                }


                $previous = $cache;

            }

        }


        return $val;

    }


    protected function setValue($key, $value, $expire) {

        $ret = $this->cache->setValue($key, $value, $expire);


        if ($ret === true) {

            $cache = $this->cache;


            while (($cache = $cache->next) != null) {

                $ret = $cache->setValue($key, $value, $expire);

                if ($ret === false)

                    return $ret;

            }

        }


        return $ret;

    }


    protected function addValue($key, $value, $expire) {

        $ret = $this->cache->addValue($key, $value, $expire);


        if ($ret === true) {

            $cache = $this->cache;


            while (($cache = $cache->next) != null) {

                $ret = $cache->addValue($key, $value, $expire);

                if ($ret === false)

                    return $ret;

            }

        }


        return $ret;

    }


    protected function deleteValue($key) {

        $ret = $this->cache->deleteValue($key);


        if ($ret === true) {

            $cache = $this->cache;


            while (($cache = $cache->next) != null) {

                $ret = $cache->deleteValue($key);

                if ($ret === false)

                    return $ret;

            }

        }


        return $ret;

    }


    protected function flushValues() {

        $ret = $this->cache->flushValues();


        if ($ret === true) {

            $cache = $this->cache;


            if (($cache = $cache->next) != null) {

                $ret = $this->cache->next->flushValues();


                if ($ret === false)

                    return $ret;

            }

        }


        return $ret;

    }


}


/**

 * Add a new property for CCache's child classes

 */

class ChainCacheBehavior extends CBehavior {


    public $nextCache = null;


    public function assignNext($cache) {

        $this->nextCache = &$cache;

        return $cache;

    }


    public function getNext() {

        if ($this->getOwner() != null)

            return $this->nextCache;

        return null;

    }


}


class GlobalCache extends CCache {


    private static $global = array();


    protected function getValue($key) {

        if (isset(self::$global[$key]))

            return self::$global[$key];


        return false;

    }


    protected function setValue($key, $value, $expire) {

        self::$global[$key] = $value;

        return true;

    }


    protected function addValue($key, $value, $expire) {

        if (!isset(self::$global[$key])) {

            self::$global[$key] = $value;

            return true;

        }


        return false;

    }


    protected function deleteValue($key) {

        unset(self::$global[$key]);

        return true;

    }


    protected function flushValues() {

        unset(self::$global);

        return true;

    }

}

How to use:

  1. Copy the code above into a file named ChainCache.php (protected/components/ChainCache.php):

  2. Define the config for caching in main.php


return array(

        //....

	'components'=>array(

                //....

                'cache'=>array(

                    'class'=>'application.components.ChainCache',

                    'chainCache'=>array(

                        array(

                            'class'=>'GlobalCache',//class in ChainCache.php

                        ),

                        array(

                            'class'=>'CDbCache',

                            'autoCreateCacheTable' => false,

                        ),

                       //define new cache classes if need

                    ),

                ),

	),

);

  1. Use in your code:



Yii::app()->cache->set('test', 'Hello');

Yii::app()->cache->get('test');

That’s all.

Thanks for reading.