让Zend Framework memcached后端缓存系统支持Tag扩展:
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@zend.com so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Cache
* @subpackage Zend_Cache_Backend
* @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
/**
* @see Zend_Cache_Backend_Interface
*/
require_once 'Zend/Cache/Backend/Interface.php';
/**
* @see Zend_Cache_Backend
*/
require_once 'Zend/Cache/Backend.php';
/**
* @package Zend_Cache
* @subpackage Zend_Cache_Backend
* @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Eshop_Cache_Backend_Memcached extends Zend_Cache_Backend implements Zend_Cache_Backend_Interface
{
/**
* Default Host IP Address or DNS
*/
const DEFAULT_HOST = '127.0.0.1';
/**
* Default port
*/
const DEFAULT_PORT = 11211;
/**
* Persistent
*/
const DEFAULT_PERSISTENT = true;
const _METADATA_CACHE_ID = 'internal_memcached_metadata';
const EMULATED_TAGS_KEY = 'Tags';
/**
* Available options
*
* =====> (array) servers :
* an array of memcached server ; each memcached server is described by an associative array :
* ‘host’ => (string) : the name of the memcached server
* ‘port’ => (int) : the port of the memcached server
* ‘persistent’ => (bool) : use or not persistent connections to this memcached server
*
* =====> (boolean) compression :
* true if you want to use on-the-fly compression
*
* @var array available options
*/
protected $_options = array(
’servers’ => array(array(
‘host’ => Zend_Cache_Backend_Memcached::DEFAULT_HOST,
‘port’ => Zend_Cache_Backend_Memcached::DEFAULT_PORT,
‘persistent’ => Zend_Cache_Backend_Memcached::DEFAULT_PERSISTENT
)),
‘compression’ => false,
‘emulateTags’ => true,
‘tag_prefix’ =>’AirShop_’
);
/**
* Memcache object
*
* @var mixed memcache object
*/
private $_memcache = null;
/**
* Metadata store object
*
* @var mixed memcache object
*/
private $_medatadaBackend;
/**
* Use metadata functionality?
*
* @var boolean
*/
private $_useMetadata = false;
/**
* Constructor
*
* @param array $options associative array of options
* @throws Zend_Cache_Exception
* @return void
*/
public function __construct(array $options = array())
{
if (!extension_loaded(’memcache’)) {
Zend_Cache::throwException(’The memcache extension must be loaded for using this backend !’);
}
parent::__construct($options);
if($this->_memcache == null){
$this->_memcache = new Memcache;
}
foreach ($this->_options['servers'] as $server) {
if (!array_key_exists(’persistent’, $server)) {
$server['persistent'] = Zend_Cache_Backend_Memcached::DEFAULT_PERSISTENT;
}
if (!array_key_exists(’port’, $server)) {
$server['port'] = Zend_Cache_Backend_Memcached::DEFAULT_PORT;
}
$this->_memcache->addServer($server['host'], $server['port'], $server['persistent']);
}
}
/**
* Test if a cache is available for the given id and (if yes) return it (false else)
*
* @param string $id Cache id
* @param boolean $doNotTestCacheValidity If set to true, the cache validity won’t be tested
* @return string|false cached datas
*/
public function load($id, $doNotTestCacheValidity = false)
{
// WARNING : $doNotTestCacheValidity is not supported !!!
if ($doNotTestCacheValidity) {
$this->_log(”Zend_Cache_Backend_Memcached::load() : \$doNotTestCacheValidity=true is unsupported by the Memcached backend”);
}
$tmp = $this->_memcache->get($id);
if (is_array($tmp)) {
return $tmp[0];
}
return false;
}
/**
* Test if a cache is available or not (for the given id)
*
* @param string $id Cache id
* @return mixed|false (a cache is not available) or “last modified” timestamp (int) of the available cache record
*/
public function test($id)
{
$tmp = $this->_memcache->get($id);
if (is_array($tmp)) {
return $tmp[1];
}
return false;
}
private function _tag(array $tags){
if(!empty($tags)){
foreach($tags as $key => $val){
$tags[$key] = $this->_options['tag_prefix'] . self::EMULATED_TAGS_KEY . ‘_’ . $val;
}
}
return $tags;
}
/**
* Save some string datas into a cache record
*
* Note : $data is always “string” (serialization is done by the
* core not by the backend)
*
* @param string $data Datas to cache
* @param string $id Cache id
* @param array $tags Array of strings, the cache record will be tagged by each string entry
* @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
* @return boolean True if no problem
*/
public function save($data, $id, $tags = array(), $specificLifetime=0)
{
// $tags = $this->_tag($tags);
$lifetime = $this->getLifetime($specificLifetime);
if ($this->_options['compression']) {
$flag = MEMCACHE_COMPRESSED;
} else {
$flag = 0;
}
if (!($result = $this->_memcache->add($id, array($data, time(), $lifetime), $flag, $lifetime))) {
$result = $this->_memcache->set($id, array($data, time(), $lifetime), $flag, $lifetime);
}
if($result && count($tags) > 0)
{
if(true)
{
$tag_list = $this->_memcache->get($this->_options['tag_prefix'] . self::EMULATED_TAGS_KEY);
$tag_list = (is_array($tag_list) ? $tag_list : array());
$tag_keys = explode(’,', $this->_options['tag_prefix'] .self::EMULATED_TAGS_KEY . ‘_’ . implode(’,’ . $this->_options['tag_prefix'] .self::EMULATED_TAGS_KEY . ‘_’, $tags));
$found_tags = $this->_memcache->get($tag_keys);
$tag_keys = array_merge(array_flip($tag_keys), $found_tags);
unset($found_tags);
foreach($tag_keys as $tag=>$data)
{
if(!is_array($data))
{
$data = array();
}
$data[] = $id;
$this->_memcache->set($tag, array_unique($data), $flag, 0);
}
$new_tags = array_diff($tags, $tag_list);
if(count($new_tags) > 0)
{
$tag_list = array_merge($tag_list, array_values($tags));
$this->_memcache->set($this->_options['tag_prefix'] .self::EMULATED_TAGS_KEY, $tag_list, $flag, 0);
}
}
else
{
if ($this->_directives['logging']) {
Zend_Log::log(”Zend_Cache_Backend_Memcached::save() : tags are unsupported by the Memcached backend”, Zend_Log::LEVEL_WARNING);
}
}
}
return $result;
}
/**
* Fetch and return metadata from metadata backend
*
* @return array
*/
private function _getMetadata()
{
$metadata = $this->_memcache->get( $this->_options['tag_prefix'] . Eshop_Cache_Backend_Memcached::EMULATED_TAGS_KEY );
if( $metadata === false )
{
$metadata = array();
}
return $metadata;
}
public function getAllTags(){
return $this->_getMetadata();
}
/**
* Save metadata to metadata backend
*
* @param unknown_type $metadata
* @return boolean True if no problem
*/
private function _saveMetadata( $metadata )
{
return $this->_medatadaBackend->save( $metadata, Eshop_Cache_Backend_Memcached::_METADATA_CACHE_ID, array(), 3153600000 );
}
/**
* Remove a cache record
*
* @param string $id Cache id
* @return boolean True if no problem
*/
public function remove($id)
{
return $this->_memcache->delete($id);
}
/**
* Clean some cache records
*
* Available modes are :
* ‘all’ (default) => remove all cache entries ($tags is not used)
* ‘old’ => remove too old cache entries ($tags is not used)
* ‘matchingTag’ => remove cache entries matching all given tags
* ($tags can be an array of strings or a single string)
* ‘matchingAnyTag’ => remove cache entries matching any given tags
* ($tags can be an array of strings or a single string)
* ‘notMatchingTag’ => remove cache entries not matching one of the given tags
* ($tags can be an array of strings or a single string)
*
* @param string $mode Clean mode
* @param array $tags Array of tags
* @return boolean True if no problem
*/
public function clean($mode = ‘all’, $tags = array())
{
//$tags = $this->_tag($tags);
if ($this->_options['compression']) {
$flag = MEMCACHE_COMPRESSED;
} else {
$flag = 0;
}
if ($mode==Zend_Cache::CLEANING_MODE_ALL) {
return $this->_memcache->flush();
}
if ($mode==Zend_Cache::CLEANING_MODE_OLD) {
if ($this->_directives['logging']) {
Zend_Log::log(”Zend_Cache_Backend_Memcached::clean() : CLEANING_MODE_OLD is unsupported by the Memcached backend”, Zend_Log::LEVEL_WARNING);
}
}
if ($mode==Zend_Cache::CLEANING_MODE_MATCHING_TAG) {
if(count($tags) > 0 && $this->_options['emulateTags'])
{
$tag_list = $this->_memcache->get($this->_options['tag_prefix'] .self::EMULATED_TAGS_KEY);
$tag_list = (is_array($tag_list) ? $tag_list : array());
$tags = array_intersect($tag_list, $tags);
$tag_keys = explode(’,', $this->_options['tag_prefix'] .self::EMULATED_TAGS_KEY . ‘_’ . implode(’,’ . $this->_options['tag_prefix'] .self::EMULATED_TAGS_KEY . ‘_’, $tags));
$tag_keys = $this->_memcache->get($tag_keys);
foreach($tag_keys as $tag)
{
if(is_array($tag))
{
foreach($tag as $key)
{
$this->_memcache->delete($key);
}
}
}
$tag_list = array_diff($tag_list, $tags);
$this->_memcache->set($this->_options['tag_prefix'] .self::EMULATED_TAGS_KEY, $tag_list, $flag, 0);
}
elseif ($this->_directives['logging']) {
Zend_Log::log(”Zend_Cache_Backend_Memcached::clean() : tags are unsupported by the Memcached backend”, Zend_Log::LEVEL_WARNING);
}
}
if ($mode==Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG) {
if($this->_options['emulateTags'])
{
$tag_list = $this->_memcache->get($this->_options['tag_prefix'] .self::EMULATED_TAGS_KEY);
$tag_list = (is_array($tag_list) ? $tag_list : array());
$tags = array_diff($tag_list, $tags);
$tag_keys = explode(’,', $this->_options['tag_prefix'] .self::EMULATED_TAGS_KEY . ‘_’ . implode(’,’ . $this->_options['tag_prefix'] .self::EMULATED_TAGS_KEY . ‘_’, $tags));
$tag_keys = $this->_memcache->get($tag_keys);
foreach($tag_keys as $tag)
{
if(is_array($tag))
{
foreach($tag as $key)
{
$this->_memcache->delete($key);
}
}
}
$tag_list = array_diff($tag_list, $tags);
$this->_memcache->set($this->_options['tag_prefix'] .self::EMULATED_TAGS_KEY, $tag_list, $flag, 0);
}
elseif ($this->_directives['logging']) {
Zend_Log::log(”Zend_Cache_Backend_Memcached::clean() : tags are unsupported by the Memcached backend”, Zend_Log::LEVEL_WARNING);
}
}
}
/**
* Clean some cache records (private method used for recursive stuff)
*
* Available modes are :
* Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
* ($tags can be an array of strings or a single string)
* Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
* ($tags can be an array of strings or a single string)
*
* @param string $mode Clean mode
* @param array $tags Array of tags
* @throws Zend_Cache_Exception
* @return boolean True if no problem
*/
private function _clean( $mode = Zend_Cache::CLEANING_MODE_ALL , $tags = array() )
{
$result = true;
$metadata = $this->_getMetadata();
foreach( $metadata as $id => $metaTags )
{
switch( $mode ) {
case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG :
$matching = false;
foreach( $tags as $tag )
{
if( in_array( $tag, $metaTags ) )
{
$result = ( $result ) && $this->remove( $id );
unset( $metadata[$id] );
break 2;
}
}
break;
case Zend_Cache::CLEANING_MODE_MATCHING_TAG :
$matching = true;
foreach( $tags as $tag )
{
if( ! in_array( $tag, $metaTags ) )
{
$matching = false;
break;
}
}
if( $matching )
{
$result = ( $result ) && ( $this->remove( $id ) );
unset( $metadata[$id] );
}
break;
case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG :
$matching = false;
foreach( $tags as $tag )
{
if( in_array( $tag, $metaTags ) )
{
$matching = true;
break;
}
}
if( ! $matching )
{
$result = ( $result ) && $this->remove( $id );
unset( $metadata[$id] );
}
break;
default :
Zend_Cache::throwException( ‘Invalid mode for clean() method’ );
break;
}
}
$this->_saveMetadata( $metadata );
return $result;
}
/**
* Return true if the automatic cleaning is available for the backend
*
* @return boolean
*/
public function isAutomaticCleaningAvailable()
{
return false;
}
/**
* Set the frontend directives
*
* @param array $directives Assoc of directives
* @throws Zend_Cache_Exception
* @return void
*/
public function setDirectives($directives)
{
parent::setDirectives($directives);
$lifetime = $this->getLifetime(false);
if ($lifetime > 2592000) {
#聽ZF-3490 : For the memcached backend, there is a lifetime limit of 30 days (2592000 seconds)
$this->_log(’memcached backend has a limit of 30 days (2592000 seconds) for the lifetime’);
}
}
//删除分页内容
public function cleanPageItemCache(){
$tag_list = $this->getAllTags();
$tag_list = $this->_tag($tag_list);
if(!empty($tag_list)){
foreach($tag_list as $tag){
$ids = $this->_memcache->get($tag);
if(!empty($ids)){
foreach ($ids as $id){
preg_match(’|’.Zend_Paginator::CACHE_TAG_PREFIX.”(\d+).*|”, $id, $page);
if(!empty($page[0])){
$this->_memcache->delete($this->_options['tag_prefix'] . $page[0]);
}
}
}
}
}
}
}