Perforce Chronicle 2012.2/486814
API Documentation

P4Cms_Cache_Backend_MemcachedTagged Class Reference

A Memcached backend that has basic support for tags. More...

List of all members.

Public Member Functions

 __construct (array $options=array())
 Constructor.
 clean ($mode=Zend_Cache::CLEANING_MODE_ALL, $tags=array())
 Clean some cache records.
 getCapabilities ()
 Return an associative array of capabilities (bools) of the backend.
 getFillingPercentage ()
 Return the filling percentage of the backend storage.
 getIds ()
 Return an array of stored cache ids.
 getIdsMatchingAnyTags ($tags=array())
 Return an array of stored cache ids which match any given tags The operation is not supported by this backend.
 getIdsMatchingTags ($tags=array())
 Return an array of stored cache ids which match given tags The operation is not supported by this backend.
 getIdsNotMatchingTags ($tags=array())
 Return an array of stored cache ids which don't match given tags The operation is not supported by this backend.
 getMetadatas ($id)
 Return an array of metadatas for the given cache id.
 getTags ()
 Return an array of stored tags The operation is not supported by this backend.
 isAutomaticCleaningAvailable ()
 Returns true if automatic cleaning is available for the backend.
 load ($id, $doNotTestCacheValidity=false)
 Returns the cached value for the given ID or false if no cache entry exists.
 remove ($id)
 Remove a cache entry by id.
 save ($data, $id, $tags=array(), $specificLifetime=false)
 Save some data to cache, will replace any existing entry of the same ID .
 test ($id)
 Checks if the given ID has a cached value or not.
 touch ($id, $extraLifetime)
 Give (if possible) an extra lifetime to the given cache id.

Public Attributes

const DEFAULT_HOST = '127.0.0.1'
 Default Server Values.
const DEFAULT_PORT = 11211
const DEFAULT_WEIGHT = 1

Protected Member Functions

 _get ($id)
 Get all data and metadata for the given ID from memcached.
 _makeTagCounterId ($tag)
 We create 'counters' in memcached for every tag we encounter.
 _makeTaggedId ($id, $tags, $force=false)
 Given an id and a list of tags, returns the 'tagged' id under which the actual data will be stored.
 _namespaceId ($id)
 This method takes care of applying the namespace, if present, as a prefix to the passed id.
 _set ($key, $value, $lifetime=0)
 Helper method to store a value; the two memcached extensions we support have slightly different argument handling and this method normalizes them.

Protected Attributes

 $_memcache = null
 $_options
 Available options.

Detailed Description

A Memcached backend that has basic support for tags.

Supports either the memcached or memecache php extensions for server communication.

We accomplish tagging support through the use of tag counters. Assuming the entry foo has the tags biz and bang we would get memcached entries similar to below: _tag-biz A counter for the biz tag (assume a value of 1 for the example) _tag-bang A counter for the bang tag (assume a value of 1 for the example) foo Really a stub, just holds the tag names used for this entry _tagged-foo-biz1-bang1 The actual data the caller is trying to store

When a user later calls load for the id 'foo' we will first load 'foo' and read out the tags that were used to store it. We will then read all of the associated tag counters. Lastly, we generate the 'tagged id' using the original id and the value of each counter.

To clear a given tag we simply increment the associated tag counter which will cause the 'tagged id' lookup described above to fail invalidating all entries with that tag.

For entries that have no tags we store them directly under the user specified id.

This system doesn't actually 'delete' anything immediately from memcached. We rely on the memcached cleanup logic to take care of these entries as they fall into disuse.

Copyright:
2011-2012 Perforce Software. All rights reserved
License:
Please see LICENSE.txt in top-level folder of this distribution.
Version:
2012.2/486814

Constructor & Destructor Documentation

P4Cms_Cache_Backend_MemcachedTagged::__construct ( array $  options = array())

Constructor.

Parameters:
array$optionsassociative array of options
Exceptions:
Zend_Cache_Exceptionif neither the memcached or memcache extensions are present
Returns:
void
    {
        parent::__construct($options);

        // for convenience users can specify a single server's settings as the direct
        // value of 'servers'; we normalize it here to always be an array of servers.
        if (isset($this->_options['servers'])) {
            $value = $this->_options['servers'];
            if (isset($value['host'])) {
                $value = array($value);
            }

            $this->setOption('servers', $value);
        }

        // select the optimal backend; throw if we can find neither
        if (extension_loaded('memcached')) {
            $this->_memcache = new Memcached;

            // add in our default options if they are not already present
            // we cannot define these when $_options is declared as the constants
            // won't exist if the memcached extension isn't loaded.
            $this->_options['client'] += array(
                Memcached::OPT_DISTRIBUTION         => Memcached::DISTRIBUTION_CONSISTENT,
                Memcached::OPT_HASH                 => Memcached::HASH_MD5,
                Memcached::OPT_LIBKETAMA_COMPATIBLE => true
            );

            // apply the client options to our instance
            foreach ($this->_options['client'] as $id => $value) {
                $this->_memcache->setOption($id, $value);
            }
        } else if (extension_loaded('memcache')) {
            $this->_memcache = new Memcache;

            // provide a default for the only supported option which is compression
            // this will be applied each time our _set method is used.
            $this->_options['client'] += array(
                'compression' => 0
            );
        } else {
            Zend_Cache::throwException('The memcached or memcache php extension must be loaded to use this backend!');
        }

        // setup memcached servers
        foreach ($this->_options['servers'] as $server) {
            if (!isset($server['host'])) {
                continue;
            }

            // add in default values if port/weight are not specified
            $server += array(
                'port'   => self::DEFAULT_PORT,
                'weight' => self::DEFAULT_WEIGHT
            );

            $this->_memcache->addServer($server['host'], $server['port'], $server['weight']);
        }
    }

Member Function Documentation

P4Cms_Cache_Backend_MemcachedTagged::_get ( id) [protected]

Get all data and metadata for the given ID from memcached.

If the entry utilized tags when it was stored this method will take care of resolving the tag counter(s) to get the actual data value back.

If successful the returned array will have the following layout: array ( 'data' => <string value>="">, 'tags' => <array of="" tags>="">, 'modified' => <unix time>="">, 'lifetime' => <lifetime when created, seconds of validity or 0 for unlimited> )

Parameters:
string$idThe item id to retrieve
Returns:
array|bool The item data & metadata or false
    {
        $directData = $this->_memcache->get($this->_namespaceId($id));

        // if we don't receive a properly formed array bail out
        if (!is_array($directData) || (!isset($directData['data']) && !isset($directData['tags']))) {
            return false;
        }

        // if data is stored directly on this id simply return result
        if (isset($directData['data'])) {
            return $directData + array('tags' => array());
        }

        // if we cannot determine the tagged id, a tag counter has
        // expired or something is amiss; bail out
        $taggedId = $this->_makeTaggedId($id, $directData['tags']);
        if (!$taggedId) {
            return false;
        }

        $taggedData = $this->_memcache->get($this->_namespaceId($taggedId));

        // if we still don't have data it is a failure
        if (!isset($taggedData['data'])) {
            return false;
        }

        // made it all the way; return the combined tag and data details
        return array_merge($directData, $taggedData);
    }
P4Cms_Cache_Backend_MemcachedTagged::_makeTagCounterId ( tag) [protected]

We create 'counters' in memcached for every tag we encounter.

This method will translate from a tag to the associated tag counter ID.

Parameters:
string$tagthe tag to get an ID for
Returns:
string the memcached ID associated with the passed tag
    {
        return '_tag-' . md5($tag);
    }
P4Cms_Cache_Backend_MemcachedTagged::_makeTaggedId ( id,
tags,
force = false 
) [protected]

Given an id and a list of tags, returns the 'tagged' id under which the actual data will be stored.

Optionally creates the 'tag counters' needed for this id if the force param is specified.

Parameters:
string$idthe plain id to utilize
array$tagsstring based tags that we want to apply to the id
bool$forcenormally we fail if any tag counters are missing, pass true to cause any missing tag counters to be created
Returns:
bool|string the tagged id or false
    {
        // we only do tagged ids for tagged content
        if (empty($tags)) {
            return false;
        }

        $tagCounterIds = array();
        foreach ($tags as $tag) {
            $tagCounterIds[] = $this->_makeTagCounterId($tag);
        }

        // read out all of the tag counters from memcached, use getMulti if possible
        if (method_exists($this->_memcache, 'getMulti')) {
            $tagCounts = $this->_memcache->getMulti($tagCounterIds);
        } else {
            $tagCounts = $this->_memcache->get($tagCounterIds);
        }

        // deal with getting a non array or only partial result back
        $tagCounts = is_array($tagCounts) ? $tagCounts : array();
        if (count($tagCounts) != count($tagCounterIds)) {
            // if we don't have force set; we have to give up at this point
            if (!$force) {
                return false;
            }

            // find the missing tags
            $missingTags = array_diff($tagCounterIds, array_keys($tagCounts));

            // use the current time as the value for all missing tag counters.
            // if we always started at 1 it would have two issues:
            // - one is a terribly obvious choice which makes it somewhat boring
            // - if a tag counter expires and we restore it to 1 we could erroneously
            //   resurrect associated entries which should really be expired as well.
            $missingTagCounts = array_fill_keys(array_values($missingTags), time());

            // ensure we include the missing tags in the full list
            $tagCounts += $missingTagCounts;

            // attempt to add all the missing tags
            foreach ($missingTagCounts as $tagId => $value) {
                // if the add fails likely someone beat us to it,
                // try and get the value they used
                if (!$this->_memcache->add($tagId, $value)) {
                    $tagCounts[$tagId] = $this->_memcache->get($tagId);

                    // if we cannot read out the value give up
                    if (!$tagCounts[$tagId]) {
                        return false;
                    }
                }
            }
        }

        // ensure the tag counts are alphabetically ordered
        uksort($tagCounts, 'strnatcmp');

        // generate a 'tagged' id to lookup the actual data
        return '_tagged-' . $id . '-'. md5(serialize($tagCounts));
    }
P4Cms_Cache_Backend_MemcachedTagged::_namespaceId ( id) [protected]

This method takes care of applying the namespace, if present, as a prefix to the passed id.

If no namespace is in use the id is simply returned unchaned.

Parameters:
string$idthe id to prefix if needed
Returns:
string the id prefix to use, blank if none
    {
        $namespace = $this->_options['namespace'];
        if (!$namespace) {
            return $id;
        }
        
        return md5($this->_options['namespace']) . '-' . $id;
    }
P4Cms_Cache_Backend_MemcachedTagged::_set ( key,
value,
lifetime = 0 
) [protected]

Helper method to store a value; the two memcached extensions we support have slightly different argument handling and this method normalizes them.

Parameters:
string$keyThe key/id to store under
mixed$valueThe value to store
int | null$lifetimeoptional - number of seconds to keep the item for 0 indicates no limit (though it can still be removed should memcached get full).
Returns:
bool true on success false otherwise
    {
        // ensure the id is namespaced if needed
        $key = $this->_namespaceId($key);

        // if the lifetime is greater than 30 days it has to be represented as
        // a unix time; do that conversion here.
        if ($lifetime > 30*24*60*60) {
            $lifetime = time() + $lifetime;
        }

        // zend cache uses null to mean forever; convert to 0 for memcached
        if ($lifetime === null) {
            $lifetime = 0;
        }

        if ($this->_memcache instanceof Memcached) {
            return $this->_memcache->set($key, $value, $lifetime);
        }

        // deal with a memcache backend which expects an extra 'flags' param
        return $this->_memcache->set($key, $value, $this->_options['client']['compression'], $lifetime);
    }
P4Cms_Cache_Backend_MemcachedTagged::clean ( mode = Zend_Cache::CLEANING_MODE_ALL,
tags = array() 
)

Clean some cache records.

Available modes are : Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used) Zend_Cache::CLEANING_MODE_OLD => not supported Zend_Cache::CLEANING_MODE_MATCHING_TAG => not supported Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => not supported Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags ($tags can be an array of strings or a single string)

Parameters:
string$modeCleaning mode
array$tagsArray of tags
Returns:
bool True if no problem
Exceptions:
Zend_Cache_Exception
    {
        switch ($mode) {
            case Zend_Cache::CLEANING_MODE_ALL:
                return $this->_memcache->flush();
                break;
            case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
                // increment the tag counters for the passed tags to invalidate any
                // related entries. increment will only succeed if there is already
                // an entry for the given id. any failures are moot as that, most
                // likely, means the tag is already expired
                foreach ($tags as $tag) {
                    $this->_memcache->increment($this->_makeTagCounterId($tag));
                }
                break;
            case Zend_Cache::CLEANING_MODE_OLD:
                $this->_log("MemcachedTagged::clean() : CLEANING_MODE_OLD is unsupported");
                break;
            case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
            case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
                $this->_log("MemcachedTagged::clean() : The MATCHING_TAG and NOT_MATCHING_TAG modes are unsupported");
                break;
           default:
                Zend_Cache::throwException('Invalid mode for clean() method');
               break;
        }

        return false;
    }
P4Cms_Cache_Backend_MemcachedTagged::getCapabilities ( )

Return an associative array of capabilities (bools) of the backend.

  • automatic_cleaning not supported
  • tags supported (though only partially in reality)
  • expired_read not supported
  • priority not supported
  • infinite_lifetime not supported
  • get_list not supported
Returns:
array associative of with capabilities
    {
        return array(
            'automatic_cleaning' => false,
            'tags'               => true,
            'expired_read'       => false,
            'priority'           => false,
            'infinite_lifetime'  => false,
            'get_list'           => false
        );
    }
P4Cms_Cache_Backend_MemcachedTagged::getFillingPercentage ( )

Return the filling percentage of the backend storage.

Returns:
int an number between 0 and 100
Exceptions:
Zend_Cache_Exception
    {
        $results = $this->_memcache->getStats();

        if ($results === false) {
            return 0;
        }

        $totalSize = null;
        $totalUsed = null;
        foreach ($results as $server => $stats) {
            if ($stats === false) {
                $this->_log("cannot get stat from " . $server);
                continue;
            }

            $size = $stats['limit_maxbytes'];
            $used = $stats['bytes'];

            // don't allow usage to exceed max size for a given server
            if ($used > $size) {
                $used = $size;
            }

            $totalSize += $size;
            $totalUsed += $used;
        }

        if ($totalSize === null || $totalUsed === null) {
            Zend_Cache::throwException('Cannot determine filling percentage');
        }

        return (int) (100 * ($totalUsed / $totalSize));
    }
P4Cms_Cache_Backend_MemcachedTagged::getIds ( )

Return an array of stored cache ids.

The operation is not supported by this backend.

Returns:
array Always returns an empty array, unsupported
    {
        $this->_log("MemcachedTagged::getIds() : unsupported by this backend");

        return array();
    }
P4Cms_Cache_Backend_MemcachedTagged::getIdsMatchingAnyTags ( tags = array())

Return an array of stored cache ids which match any given tags The operation is not supported by this backend.

Parameters:
array$tagsarray of tags
Returns:
array Always returns an empty array, unsupported
    {
        $this->_log("MemcachedTagged::getIdsMatchingAnyTags() : unsupported by this backend");

        return array();
    }
P4Cms_Cache_Backend_MemcachedTagged::getIdsMatchingTags ( tags = array())

Return an array of stored cache ids which match given tags The operation is not supported by this backend.

Parameters:
array$tagsarray of tags
Returns:
array Always returns an empty array, unsupported
    {
        $this->_log("MemcachedTagged::getIdsMatchingTags() : unsupported by this backend");

        return array();
    }
P4Cms_Cache_Backend_MemcachedTagged::getIdsNotMatchingTags ( tags = array())

Return an array of stored cache ids which don't match given tags The operation is not supported by this backend.

Parameters:
array$tagsarray of tags
Returns:
array Always returns an empty array, unsupported
    {
        $this->_log("MemcachedTagged::getIdsNotMatchingTags() : unsupported by this backend");

        return array();
    }
P4Cms_Cache_Backend_MemcachedTagged::getMetadatas ( id)

Return an array of metadatas for the given cache id.

The array must include these keys :

  • expire : the expire timestamp
  • tags : a string array of tags
  • mtime : timestamp of last modification time
Parameters:
string$idcache id
Returns:
array array of metadatas (false if the cache id is not found)
    {
        $entry = $this->_get($id);

        if (isset($entry['modified'], $entry['lifetime'], $entry['tags'])) {
            return array(
                'expire' => $entry['modified'] + $entry['lifetime'],
                'tags'   => $entry['tags'],
                'mtime'  => $entry['modified']
            );
        }

        return false;
    }
P4Cms_Cache_Backend_MemcachedTagged::getTags ( )

Return an array of stored tags The operation is not supported by this backend.

Returns:
array Always returns an empty array, unsupported
    {
        $this->_log("MemcachedTagged::getTags() : unsupported by this backend");

        return array();
    }
P4Cms_Cache_Backend_MemcachedTagged::isAutomaticCleaningAvailable ( )

Returns true if automatic cleaning is available for the backend.

Returns:
bool Always returns false for this type of backend
    {
        return false;
    }
P4Cms_Cache_Backend_MemcachedTagged::load ( id,
doNotTestCacheValidity = false 
)

Returns the cached value for the given ID or false if no cache entry exists.

Parameters:
string$idCache id to retrieve
boolean$doNotTestCacheValidityHas no effect on this backend
Returns:
string|false cached data or false
    {
        $entry = $this->_get($id);

        if (isset($entry['data'])) {
            return $entry['data'];
        }

        return false;
    }
P4Cms_Cache_Backend_MemcachedTagged::remove ( id)

Remove a cache entry by id.

Parameters:
string$idCache id
Returns:
boolean True if entry was removed, false if it didn't exist
    {
        return $this->_memcache->delete($id);
    }
P4Cms_Cache_Backend_MemcachedTagged::save ( data,
id,
tags = array(),
specificLifetime = false 
)

Save some data to cache, will replace any existing entry of the same ID .

Note: $data is always a "string" (serialization is done by the core not by the backend)

Parameters:
string$dataThe value to cache
string$idCache id
array$tagsArray of strings, the cache record will be tagged by each string entry
int | bool | null$specificLifetimeNumber of seconds to keep entry for or 0/null for unlimited or false to use the default lifetime
Returns:
bool True if entry was cached, false otherwise
    {
        // memcached supports a max data size of 1 meg; don't bother
        // pushing anything larger over the wire as it will fail.
        if (strlen($data) > 1024*1024) {
            $this->_log("MemcachedTagged::save() skipped, data over 1 meg");
            return false;
        }

        $lifetime = $this->getLifetime($specificLifetime);

        $entry    = array(
            'data'      => $data,
            'modified'  => time(),
            'lifetime'  => $lifetime
        );

        // if no tags have been specified just store the entry details directly
        // on its specified id.
        // if tags are present, we instead store the list of tags on the passed
        // id and locate the entry under a new 'taggedId' we generate.
        if (empty($tags)) {
            $result   = $this->_set($id, $entry, $lifetime);
        } else {
            $taggedId = $this->_makeTaggedId($id, $tags, true);
            $result   = $taggedId && $this->_set($id,       array('tags' => $tags), $lifetime);
            $result   = $result   && $this->_set($taggedId, $entry,                 $lifetime);
        }

        if ($result === false) {
            $this->_log("MemcachedTagged::save() failed");
        }

        return $result;
    }
P4Cms_Cache_Backend_MemcachedTagged::test ( id)

Checks if the given ID has a cached value or not.

Note this will pull down the entries value in the process.

Parameters:
string$idCache id
Returns:
int|false "last modified" timestamp (int) if available otherwise false
    {
        $entry = $this->_get($id);

        // if this looks like a valid entry (has data) and
        // has a modified date we are happy; return it.
        if (isset($entry['data'], $entry['modified'])) {
            return (int)$entry['modified'];
        }

        // the item was expired or invalid, fail out
        return false;
    }
P4Cms_Cache_Backend_MemcachedTagged::touch ( id,
extraLifetime 
)

Give (if possible) an extra lifetime to the given cache id.

Parameters:
string$idcache id
int$extraLifetimenumber of seconds to add to the current lifetime
Returns:
bool true if ok false otherwise
    {
        $entry = $this->_get($id);

        if (isset($entry['data'], $entry['modified'], $entry['lifetime'])) {
            $newLifetime = $entry['lifetime'] - (time() - $entry['modified']) + $extraLifetime;

            if ($newLifetime <= 0) {
                return false;
            }

            return $this->save($entry['data'], $id, $entry['tags'], $newLifetime);
        }

        return false;
    }

Member Data Documentation

P4Cms_Cache_Backend_MemcachedTagged::$_memcache = null [protected]
P4Cms_Cache_Backend_MemcachedTagged::$_options [protected]
Initial value:
 array(
        'namespace' => null,
        'servers'   => array(
            array(
                'host'   => self::DEFAULT_HOST,
                'port'   => self::DEFAULT_PORT,
                'weight' => self::DEFAULT_WEIGHT,
            )
        ),
        'client'    => array()
    )

Available options.

(array) servers : an array of memcached server(s) ; each memcached server is described by an associative array : 'host' => (string) : the dns or ip address of the memcached server 'port' => (int) : the port of the memcached server 'weight' => (int) : number of buckets to create for this server which in turn control its probability of it being selected. The probability is relative to the total weight of all servers. (array) client : if using the memcached php extension

See also:
http://php.net/manual/memcached.constants.php for valid options and their associated values. default options are set for Distribution, Hash and Libketama. if using the memcache php extension the only option is 'compression' which can have the value 0 or MEMCACHE_COMPRESSED (0 being the default).

Default Server Values.


The documentation for this class was generated from the following file: