Sunday, January 24, 2010

Caching data


In this example, we cache data using the following front-ends: core, standard output, class method, function. Data are cached in a file or in a database.

Components used in this example
Working with caches

class MyCache
{

    public static $frontend = array('Core', 'Class', 'Object', 'Function', 'Output');
    public static $backend = array('File', 'Sqlite');

Processing the cache request
  • We get the front-end, the back-end, the record identifier, the data or parameter, the tags, and the lifetime.
  • We set the cache.
  • We get the record identifiers currently cached.
  • We perform the action on the cache.
  • We refresh the list of record identifiers currently cached.
  • If we catch an exception, we return the error message.

    public function process()
    {
        // We get the front-end, the back-end, the record identifier,
        // the data or parameter, the tags, and the lifetime.
        list($frontend, $backend, $action, $id, $param, $tags, $lifetime) =
            $this->_getParameters();

        $ids = array();
        $params = array();

        try {
            // We set the cache.
            $cache = $this->_setCache($frontend, $backend, $lifetime);
            // We get the record identifiers currently cached.
            $ids = $this->_getIds($cache);

            // We perform the action on the cache.
            switch($action) {
                case 'clean all':
                case 'clean matchingAnyTag':
                case 'clean matchingTag':
                case 'clean notMatchingTag':
                case 'clean old':
                    list(, $mode) = explode(' ', $action);
                    $result = $cache->clean($mode, $tags);
                    break;

                case 'getBackend':
                case 'getFillingPercentage':
                case 'getIds':
                case 'getTags':
                    $result = $cache->$action();
                    break;

                case 'getIdsMatchingTags':
                case 'getIdsNotMatchingTags':
                    $result = $cache->$action($tags);
                    break;

                case 'getMetadatas':
                case 'load':
                case 'remove':
                case 'test':
                    $result = $cache->$action($id);
                    break;

                case 'touch 3':
                case 'touch 10':
                case 'touch 60':
                case 'touch 3600':
                    list(, $extraLifetime) = explode(' ', $action);
                    $result = $cache->touch($id, $extraLifetime);
                    break;

                case 'Cache':
                    $result = $this->_cache($cache, $frontend, $backend, $tags,
                        $lifetime, $param);
                    $id = null;
                    break;

                case 'Set':
                default:
                    $result = "The $frontend / $backend cache is set";
            }

            // We refresh the list of record identifiers currently cached.
            $ids = $this->_getIds($cache);

        } catch (Exception $e) {
            // If we catch an exception, we return the error message.
            $result = $e->getMessage();
        }

        return array($frontend, $backend, $action, $id, $param, $lifetime, $ids, $result);
    }

Extraction of the parameters from the GET request

    private function _getParameters()
    {
        $frontend = isset($_GET['frontend'])? $_GET['frontend'] : 'Core';
        $backend = isset($_GET['backend'])? $_GET['backend'] : 'File';
        $id = isset($_GET['id'])? $_GET['id'] : null;
        $param = isset($_GET['param'])? $_GET['param'] : null;
        $tags = isset($_GET['tags'])? $_GET['tags'] : array();
        $lifetime = isset($_GET['lifetime'])? $_GET['lifetime'] : 300;

        isset($_GET['submit']) and $action = $_GET['submit'] or
        $action = isset($_GET['action'])? $_GET['action'] : null;

        return array($frontend, $backend, $action, $id, $param, $tags, $lifetime);
    }

Setting the cache

    private function _setCache($frontend, $backend)
    {
        $frontendOptions = array(
            'lifetime' => 3600,
            'automatic_serialization' => true,
            'cache_id_prefix' => $this->_setPrefix($frontend),
            );

        switch($frontend) {
            case 'Class':
                $frontendOptions['cached_entity'] = __CLASS__;
                break;

            case 'Object':
                $frontend = 'Class';
                $frontendOptions['cached_entity'] = $this;
                break;
        }

        switch($backend) {
            case 'Sqlite':
                $backendOptions['cache_db_complete_path'] = 'data/cache/cache.sqlite';
                break;

            case 'File':
            default:
                $backendOptions['cache_dir'] = 'data/cache';
                break;
        }

        return Zend_Cache::factory($frontend, $backend, $frontendOptions, $backendOptions);
    }

Getting the record identifiers currently cached without prefixes

    private function _getIds($cache)
    {
        $ids = $cache->getIds();
        $prefixes = implode('|', self::$frontend);
        $fct = create_function('$s', "return !preg_match('~($prefixes)~', \$s);");

        return array_filter($ids, $fct);
    }

Caching data
  • We cache the data or parameter along with the back-end, the front-end, the tags, the save method, and the lifetime.

    private function _cache($cache, $frontend, $backend, $tags, $lifetime, $param)
    {
        // We cache the data or parameter along with the back-end, the front-end,
        // the tags, the save method, and the lifetime.

        switch($frontend) {
            case 'Core':
                if (!($isCached = $data = $cache->load($param))) {
                    $data = self::_makeData($frontend, $backend, $tags, 'save()',
                        $lifetime, $param);
                    $cache->save($data, $param, $tags, $lifetime);
                }
                break;

            case 'Class':
                $method = __CLASS__ . '::' . __FUNCTION__ . '()';
                $lifetime and $cache->setSpecificLifetime($lifetime);
                $tags and $cache->setTagsArray($tags);
                $time = time();
                $data = $cache->staticMethod($frontend, $backend, $tags, $method,
                    $lifetime, $param);
                $isCached = $data['settings']['timestamp'] < $time;
                break;

            case 'Object':
                $method = '$cache->' . __FUNCTION__ . '()';
                $lifetime and $cache->setSpecificLifetime($lifetime);
                $tags and $cache->setTagsArray($tags);
                $time = time();
                $data = $cache->nonStaticMethod($frontend, $backend, $tags, $method,
                    $lifetime, $param);
                $isCached = $data['settings']['timestamp'] < $time;
                break;

            case 'Function':
                $method = __FUNCTION__ . '()';
                $parameters = array($frontend, $backend, $tags, $method, $lifetime, $param);
                $time = time();
                $data = $cache->call('myFunction', $parameters, $tags, $lifetime);
                $isCached = $data['settings']['timestamp'] < $time;
                break;

            case 'Output':
                if (!($isCached = $data = $cache->start($param, false, false))) {
                    $data = self::_makeData($frontend, $backend, $tags, 'start()/end()',
                        $lifetime, $param);
                    print_r($data);
                    $cache->end($tags, $lifetime, null, false);
                }
                break;
        }

        $result[] = $isCached?
            'THE DATA WAS RETRIEVED FROM THE CACHE.' :
            'THE DATA HAS JUST BEEN ADDED TO THE CACHE.';
        $result[] = $data;

        return $result;
    }

Static method to be cached

    public static function staticMethod($frontend, $backend, $tags, $method,
        $lifetime, $param)
    {
        return self::_makeData($frontend, $backend, $tags, $method, $lifetime, $param);
    }

Non-static method to be cached

    public static function nonStaticMethod($frontend, $backend, $tags, $method,
        $lifetime, $param)
    {
        return self::_makeData($frontend, $backend, $tags, $method, $lifetime, $param);
    }

Putting the data together

    public static function _makeData($frontend, $backend, $tags, $method, $lifetime, $param)
    {
        $data['DATA'] = $param;
        $data['settings'] = array(
            'date' => date(DATE_COOKIE),
            'lifetime' => $lifetime,
            'frontend' => $frontend,
            'backend' => $backend,
            'method' => $method,
            'timestamp' => time(),
            'tags' => $tags,
        );

        return $data;
    }

Building the prefix of identifiers
  • We store a random number in a cookie to identify a user.
  • We build the prefix with the user identifier and the name of front-end.

    private function _setPrefix($frontend)
    {
        // We store a random number in a cookie to identify a user.
        if (empty($_COOKIE['cache'])) {
            $user = rand(1000, 9999);
            setcookie('cache', $user, time() + 3600 * 24, '/');
        } else {
           $user = $_COOKIE['cache'];
        }

        // We build the prefix with the user identifier and the name of front-end.
        return "$user$frontend";
    }


}

No comments:

Post a Comment