View Source

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{zone-template-instance:ZFPROP:Proposal Zone Template}

{zone-data:component-name}
Zend_Cache Refactoring
{zone-data}

{zone-data:proposer-list}
[Marc Bennewitz|http://framework.zend.com/wiki/display/~mabe]
{zone-data}

{zone-data:liaison}
TBD
{zone-data}

{zone-data:revision}
0.1 - 6 Jun 2009: Initial Draft.
0.2 - 8 Jun 2009: Finished Proposal.
0.3 - 23 Jun 2009: Only one namespace option.
0.4 - 23 Jun 2009: Handling of multiple cache ids.
0.5 - 20 Jul 2009: Added how to instantiate without factory. (UC: Own Backend)
0.6 - 30 Jul 2009: Added namespace use case
0.7 - 18 Sep 2009: Added functions replace & add
0.8 - 08 Oct 2009: Added function optimize and moved automatic optimization from sqlite backend to core fontend
0.9 - 23 Oct 2009: Added Zend_Serializer proposal on references
0.10 - 23 Oct 2009: Added increment/decrement functionality
0.11 - 21 Jan 2010: Multiple Interface & getDelayed/fetch interface 6 backwards compatibility
{zone-data}

{zone-data:overview}
Refactoring Zend_Cache for ease of use, more functionality and optimized performance.
{zone-data}

{zone-data:references}
* [Zend_Cache|http://framework.zend.com/manual/en/zend.cache.html]
* [Zend_Cache_Frontend_Page Etag support - Kevin Schroeder|http://framework.zend.com/wiki/display/ZFPROP/Zend_Cache_Frontend_Page+Etag+support+-+Kevin+Schroeder]
* [Zend_Serializer - Marc+Bennewitz|http://framework.zend.com/wiki/display/ZFPROP/Zend_Serializer+-+Marc+Bennewitz] used to select a specific serializer like php, igbinary etc. on backends which needs serialization to store different datatypes.

*Source Code (In Development)*
svn://marc-bennewitz.de/zf-proposals
{zone-data}

{zone-data:requirements}
* The Zend_Cache factory *will* use a configurable plugin loader to load frontends and backends.
* The Zend_Cache factory *will* only accept one argument for options.
* The frontend *will* implement tagging if it isn't done by backend.
* The frontend *will no longer* serialize data. This *must be* done by backend.
* The Zend_Cache *can handle* an array of cache ids to get/set/remove multiple cache ids.
* The compontent *will* accept all php datatypes (incl. NULL) to cache them.
* Matching modes *should be* joined with the binary OR (|) operator for method clean and getIds.
* Only one namespace/prefix option *will be* used.
* The sqlite backend *will* support differents php extensions and versions: php_sqlite, sqlite, sqlite3.
* The backends *will* have just one interface to implement.
* The core frontend *will be* moved to the frontend folder -> Zend_Cache_Frontend_Core.
* The Test backend *will be* removed and the tests have to use the Array backend.
{zone-data}

{zone-data:dependencies}
* Zend_Cache
* Zend_Exception
* Zend_Loader_PluginLoader
* Zend_Serializer
{zone-data}

{zone-data:operation}
*Serialization:*
* The serialization has been moved to backend.
* Backends that only supports to handle strings there is a option named "serializer".\\This can be an instance of Zend_Serializer_Adapter_AdapterInterface or a string used to instantiate the serializer with Zend_Serializer::factory.\\Additionally you can disable serialization by set this option to NULL.
* Backends supporting different datatypes doesn't implement this option but memcached.\\Memcached has own serializer selectable with Memcache::SERIALIZER_* constants. To use the build-in serializer you can set the serializer option to one of the constants or you can handle this options same as other backends.


*deprecated methods:*
* Zend_Cache_Core::setLifetime - Please use the option "ttl" instead.
* Zend_Cache_Core::load - Please use get instead.
** Old: load($id, $doNotTestCacheValidity = false, $doNotUnserialize = false)
** New: get($id = null, array $opts = array())
* Zend_Cache_Core::test - Please use exist or info instead.
** Old: test($id = null) : return mtime or false
** New: exist($id = null, array $opts = array()) : return boolean
** To get mtime use info($id, array $opts=array()) : return array with "mtime" key
* Zend_Cache_Core::save - Please use set/add/replace instead.
** Old: save($data, $id = null, $tags = array(), $specificLifetime = false, $priority = 8)
** New: set($data, $id = null, array $opts=array())
* Zend_Cache_Core::clean - Please use clear instead.
** Old: clean($mode = Zend_Cache::CLEANING_MODE_ALL, array $tags = array())
** New: clear($mode = Zend_Cache::MATCHING_ALL, array $opts = array())
* Zend_Cache_Core::getIdsMatchingTags - Please use find instead.
** Use find(Zend_Cache::MATCHING_ACTIVE | Zend_Cache::MATCHING_TAGS, array('tags' => $tags))
* Zend_Cache_Core::getIdsNotMatchingTags - Please use find instead.
** Use find(Zend_Cache::MATCHING_ACTIVE | Zend_Cache::MATCHING_NO_TAGS, array('tags' => $tags))
* Zend_Cache_Core::getIds - Please use find instead.
** Use find(Zend_Cache::MATCHING_ACTIVE)
* Zend_Cache_Core::getFillingPercentage - Please use status instead.
** Use status() - return array('total' => <number>, 'free' => <number>)
* Zend_Cache_Core::getTags - Please use info instead.
* Zend_Cache_Core::getMetadatas - Please use info instead.
** Use info($id) - return array('mtime' => <int>, 'tags' => <array>, 'ttl' => <int>[, + backend spezific information])

Bc of all deprecated methods are only implemented on core frontend which is normally the base class.

*deprecated core options:*
* cache_id_prefix - Please use "namespace" instead.
* automatic_serialization - The option "automatic_serialization" has been removed. There is no more usage of it.
** Now backends who doesn't support different datatypes implement new option "serializer" (see Zend_Serializer)
* automatic_cleaning_factor - Please use "automatic_clearing_factor" instead.
** Because the method name has changed
* lifetime - Please use "ttl" instead.
** Zend_Cache was the only caching system who named it lifetime. On all other systems it is named ttl


*deprecated file backend options:*
* read_control_type - Please use "read_control_algo" instead.
* hashed_directory_level - Please use "directory_level" instead.
* hashed_directory_umask - Please use "directory_perm" or "directory_umask" instead.
** hashed_directory_umask wasn't a really umask it was a permission value
** The new directory_perm set permissions by the same value
** The new directory_umask set permissions by using umask
* file_name_prefix - Please use "namespace" option of core frontend instead.
* cache_file_umask - Please use "file_perm" or "file_umask" instead.
** Same as hashed_directory_umask
* metadatas_array_max_size - Option completely removed

*deprecated sqlite backend options:*
* cache_db_complete_path - Please use "dsn" instead.
* automatic_vacuum_factor - Please use "automatic_optimizing_factor" of core frontend instead.
** Normalized backend optimization interface

*Changes on memcache(d) backend:*
* Renamed Zend_Cache_Backend_Memcached to Zend_Cache_Backend_Memcache (uses memcache php extension)
* added Zend_Cache_Backend_Memcached (uses memcached php extension)
** TODO: How can bc done with this naming change ???

*deprecated class frontend options:*
* cached_methods - Please use "cache_callbacks" instead.
* non_cached_methods - Please use "non_cache_callbacks" instead.
** Now the class frontend extends the new callback frontend

*deprecated function frontend options:*
* cache_functions - Please use "cache_callbacks" instead.
* non_cache_functions - Please use "non_cache_callbacks" instead.
** Now the class frontend extends the new callback frontend


{zone-data}

{zone-data:milestones}
* Milestone 1 \[DONE\]: Finish proposal
* Milestone 2 \[DONE\]: Working prototype
* Milestone 3: Prototype checked into the incubator
* Milestone 4: Unit tests exist finished and component is working
* Milestone 5: Initial documentation exists
* Milestone 6: Changed related components
* Milestone 7: Moved to core.
{zone-data}

{zone-data:class-list}
* Zend_Cache
* Zend_Cache_Exception

* Zend_Cache_Frontend_Core *renamed*
* Zend_Cache_Frontend_Callback *new*
* Zend_Cache_Frontend_Function (extends Zend_Cache_Frontend_Callback)
* Zend_Cache_Frontend_Class (extends Zend_Cache_Frontend_Callback)
* Zend_Cache_Frontend_File
* Zend_Cache_Frontend_Output
* Zend_Cache_Frontend_Page

* Zend_Cache_Backend_Interface
* Zend_Cache_Backend_Abstract *renamed*
* Zend_Cache_Backend_Array *new*
* Zend_Cache_Backend_Apc
* Zend_Cache_Backend_File
* Zend_Cache_Backend_Memcache *renamed*
* Zend_Cache_Backend_Memcached *new*
* Zend_Cache_Backend_Xcache
* Zend_Cache_Backend_Sqlite
* Zend_Cache_Backend_SqliteAdapter_Abstract *new*
* Zend_Cache_Backend_SqliteAdapter_Sqlite *new*
* Zend_Cache_Backend_SqliteAdapter_Sqlite3 *new*
* Zend_Cache_Backend_SqliteAdapter_PdoSqlite *new*
* Zend_Cache_Backend_ZendPlatform
* Zend_Cache_Backend_ZendServer
* Zend_Cache_Backend_ZendServer_Disk
* Zend_Cache_Backend_ZendServer_ShMem
* Zend_Cache_Backend_TwoLevels
{zone-data}

{zone-data:use-cases}


{composition-setup}
{deck:id=Changes}
{card:label=Factory}
{code}
$options = array(
'write_control' => false,
'automatic_cleaning_factor' => 0,
'ttl' => 3600,
'throw_exceptions' => true,
'logging' => false,
'ignore_user_abort' => false,
'namespace' => 'zfcache',

'cache_dir' => sys_get_temp_dir(),
'file_perm' => 0600,
'file_locking' => true,
'hashed_directory_level' => 3,
'hashed_directory_algo' => 'md5',
'hashed_directory_perm' => 0700,
'read_control' => true,
'read_control_algo' => 'crc32',
)
$cache = Zend_Cache::factory('Core', 'File', $options);
{code}
{card}

{card:label=Own Backend}
{code}
// write the own backend
class My_NiceBackend implements Zend_Cache_Backend_Interface {
// ...
}
{code}
{code}
// 1. opportunity
Zend_Cache::getBackendLoader()->addPrefixPath('My', dirname(__FILE__));
$cache = Zend_Cache::factory('Core', 'NiceBackend', $options);

// 2. opportunity
require_once 'My/NiceBackend.php';
$cache = Zend_Cache::factory('Core', new My_NiceBackend(), $options);

// 3. without factory
require_once 'Zend/Cache/Frontend/Core.php';
require_once 'My/NiceBackend.php';
$cache = new Zend_Cache_Frontend_Core(new My_NiceBackend(), $options);
{code}
{card}

{card:label=Datatypes}
Store different datatypes
{code}
$cache = Zend_Cache::factory('Core', 'Array', $options);

// handle all datatypes without NULL's
$rs = $cache->load('id');
if ($rs ==! null) {
$rs = true;
$cache->save($rs);
}

// to handle stored NULL's you have to test the cache before
if ($cache->test('id')) {
$rs = $cache->load();
} else {
$rs = NULL;
$cache->save($rs);
}
{code}
{card}

{card:label=Frontend Tagging}
{code}
$cache = Zend_Cache::factory('Core', 'Memcache', $options);

// Store tags on current backend
// - If the current backend doen't support tagging the current backend will be used as tag handler
$cache->save('data', 'id', array('tags' => array('tag1', 'tag2')));

// Use a different backend as tag handler
$tagHandler = new Zend_Cache_Backend_File();
$cache->setOption('tag_handler', $tagHandler);
$cache->save('data', 'id', array('tags' => array('tag1', 'tag2')));

// Use a different namespace to store tag informations
$cache->setOption('tag_namespace', 'mytags');
$cache->save('data', 'id', array('tags' => array('tag1', 'tag2')));
{code}
{card}

{card:label=Multi calls}
{code}
$cache = Zend_Cache::factory('Core', 'Memcached', $options);

// load multiple cache ids
$cache->load(array('id1', 'id2', 'id3')));
// -> returns an array of all valid cache ids an his content

// test multiple cache ids
$cache->test(array('id1', 'id2', 'id3'));
// -> returns an array of all requested cache ids and the last modification time or false

// remove multiple cache ids
$cache->remove(array('id1', 'id2', 'id3'));

// get multiple metadata
$cache->getMetadata(array('id1', 'id2', 'id3'));
// -> returns an array of all requested cache ids and the metadata or false
{code}
{card}

{card:label=Callback Frontend}
{code}
$cache = Zend_Cache::factory('Callback', 'File', $options);

$rs = $cache->call(array('MyClass', 'myMethod'), array('arg1', 'arg2'));
{code}
{card}

{card:label=ETag support}
{code}
$options = array(
'http_etag' => true,
'http_etag_hash_algo' => 'crc32'
);
$cache = Zend_Cache::factory('Page', 'File', $options);

// If cache is hit an and the request headers (HTTP_IF_NONE_MATCH & HTTP_IF_MODIFIED_SINCE) matches
// an 304 header will be send and the script stops executing with exit
// If the cache isn't hit the etag will generate by http_etag_hash_algo (Only if not send before)
// - Only the ETag header will be send
$cache->start();

// do something

// to change the http status code use:
$cache->setOption('http_status_code', 204);
// or
$cache->setOption('http_status_code', '204 No Content');
// or
header('HTTP/1.1 204 No Content');
header('Status: 204');

// to generate you own ETag header send it:
header('ETag: "myEtag"');
{code}
{card}

{card:label=namespace}
{code}
// set the default namespace
$options = array(
'namespace' => 'zf',
);
$cache = Zend_Cache::factory('Core', 'File', $options);
// Now $cache only reads and writes data with the namespace "zf"

// To change the namespace you can overwrite the default
$cache->setOption('namespace', 'new');
// or you can overwrite it directly for only one call
$cache->load('id', array('namespace' => 'new'))
{code}
{card}

{deck}
{zone-data}

{zone-data:skeletons}
{composition-setup}
{deck:id=Skeletons}
{card:label=Zend_Cache}
{code}
abstract class Zend_Cache
{

/**
* Find expired cache ids
*
* @var int Matching expired
*/
const MATCHING_EXPIRED = 01;

/**
* Find not expired cache ids
*
* @var int Matching not expired cache ids
*/
const MATCHING_ACTIVE = 02;

/**
* Find all cache ids
* NOTE: This is the same as (MATCHING_EXPIRED | MATCHING_TAGS)
*
* @var int Matching expired and not expired cache id
*/
const MATCHING_ALL = 03;

/**
* Find cache ids matching all of the given tags
*
* @var int Matching all tags
*/
const MATCHING_TAGS = 010;

/**
* Find cache ids matching none of the given tags
*
* @var int matching none tags
*/
const MATCHING_NO_TAGS = 020;

/**
* Find cache ids matching any of the given tags
*
* @var int matching any tags
*/
const MATCHING_ANY_TAGS = 040;

/**
* Constants used on clean() - only for backwards compatibility.
* @deprecated
*/
const CLEANING_MODE_ALL = 'all';
const CLEANING_MODE_OLD = 'old';
const CLEANING_MODE_MATCHING_TAG = 'matchingTag';
const CLEANING_MODE_NOT_MATCHING_TAG = 'notMatchingTag';
const CLEANING_MODE_MATCHING_ANY_TAG = 'matchingAnyTag';

/**
* frontend loader
*
* @var null|Zend_Loader_PluginLoader
*/
private static $_frontendLoader = null;

/**
* backend loader
*
* @var null|Zend_Loader_PluginLoader
*/
private static $_backendLoader = null;

/**
* Factory
*
* @param string $frontend frontend name
* @param string|Zend_Cache_Backend_Interface $backend backend name (string) or backend object
* @param array|Zend_Config $options frontend and backend options
* @return Zend_Cache_Frontend_Core
* @throws Zend_Cache_Exception
*/
public static function factory($frontend, $backend, $options = array(), $backendOptions = array())
{
// create backend
if ($backend instanceof Zend_Cache_Backend_Interface) {
$backendObject = $backend;
} else {
try {
$backendLoader = self::getBackendLoader();
$backendClass = $backendLoader->load($backend);
} catch (Exception $e) {
throw new Zend_Cache_Exception('Can\'t load backend "'.$backend.'"', 0, $e);
}

if (!in_array('Zend_Cache_Backend_Interface', class_implements($backendClass))) {
require_once 'Zend/Serializer/Exception.php';
throw new Zend_Serializer_Exception('The backend class "'.$backendClass.'" must implement Zend_Cache_Backend_Interface');
}

$backendObject = new $backendClass();
}

// set backend options
$backendObject->setOptions($backendOptions);

// create frontend
if ($frontend instanceof Zend_Cache_Frontend_Core) {
$frontendObj = $frontend;
$frontendObj->setOptions($options);
} else {
try {
$frontendLoader = self::getFrontendLoader();
$frontendClass = $frontendLoader->load($frontend);
} catch (Exception $e) {
throw new Zend_Cache_Exception('Can\'t load frontend "'.$frontend.'"', 0, $e);
}

if ($frontendClass != 'Zend_Cache_Frontend_Core') {
$extends = class_parents($frontendClass);
if (!in_array('Zend_Cache_Frontend_Core', class_parents($frontendClass))) {
throw new Zend_Cache_Exception('The frontend class "'.$frontendClass.'" must extends Zend_Cache_Frontend_Core');
}
}

$frontendObj = new $frontendClass($backendObject, $options);
}

return $frontendObj;
}

/**
* Get the frontend loader object
*
* @return Zend_Loader_PluginLoader
*/
public static function getFrontendLoader()
{
if (!self::$_frontendLoader) {
$loader = new Zend_Loader_PluginLoader();
$loader->addPrefixPath('Zend_Cache_Frontend', dirname(__FILE__).'/Cache/Frontend');
self::$_frontendLoader = $loader;
}
return self::$_frontendLoader;
}

/**
* Set frontend loader
*
* @param Zend_Loader_PluginLoader $pluginLoader
*/
public static function setFrontendLoader(Zend_Loader_PluginLoader $pluginLoader)
{
self::$_frontendLoader = $pluginLoader;
}

/**
* Get the backend loader object
*
* @return Zend_Loader_PluginLoader
*/
public static function getBackendLoader()
{
if (!self::$_backendLoader) {
$loader = new Zend_Loader_PluginLoader();
$loader->addPrefixPath('Zend_Cache_Backend', dirname(__FILE__).'/Cache/Backend');
self::$_backendLoader = $loader;
}
return self::$_backendLoader;
}

/**
* Set backend loader
*
* @param Zend_Loader_PluginLoader $pluginLoader
*/
public static function setBackendLoader(Zend_Loader_PluginLoader $pluginLoader)
{
self::$_backendLoader = $pluginLoader;
}

}
{code}
{card}

{card:label=Zend_Cache_Frontend_Core}
{code}
class Zend_Cache_Core
{

/**
* @var Zend_Cache_Backend_Interface
*/
protected $_backend = null;

/**
* Available options
*
* ====> (boolean) caching :
* - Enable / disable caching
* (can be very useful for the debug of cached scripts)
*
* =====> (string|null) namespace :
* - The default namespace
*
* ====> (int) ttl :
* - Cache lifetime (in seconds)
* - If null, the cache is valid forever. (If supported by backend else use the longes ttl)
*
* ====> (null|Zend_Cache_Backend_Interface) tag_handler:
* - Handle tags on this backend if the cache backend doesn't support tagging.
* - The default handler is the current used backend
*
* ====> (string|null) tag_namespace:
* - Use a namespace to store tags on handler.
*
* ====> (int) automatic_clearing_factor :
* - Calls method "clear" with expired matching mode when a new cache file is written :
* 0 => no automatic cache clearing
* 1 => systematic cache clearing
* x (integer) > 1 => automatic clearing randomly 1 times on x cache write
*
* ====> (int) automatic_optimizing_factor :
* - Calls method "optimize" when a new cache file is written :
* 0 => no automatic cache optimizing
* 1 => systematic cache optimizing
* x (integer) > 1 => automatic optimizing randomly 1 times on x cache write
*
* ====> (boolean) write_control :
* - Enable / disable write control (the cache is read just after writing to detect corrupt entries)
* - Enable write control will lightly slow the cache writing but not the cache reading
* Write control can detect some corrupt cache files but maybe it's not a perfect control
*
* ====> (boolean) ignore_user_abort
* - If set to true, the core will set the ignore_user_abort PHP flag inside the
* set() method to avoid cache corruptions in some cases (default false)
*
* ====> (boolean) logging :
* - If set to true, logging is activated (but the system is slower)
*
* ====> (boolean) logger :
* -
*
* ====> (boolean) throw_exceptions :
* -
*
* @var array
*/
protected $_options = array(
'caching' => true,
'namespace' => null,
'ttl' => 3600,
'tag_handler' => null,
'tag_namespace' => null,
'automatic_clearing_factor' => 0,
'automatic_optimizing_factor' => 0,
'write_control' => false,
'ignore_user_abort' => false,
'throw_exceptions' => false,
'logging' => false,
'logger' => null,
);

/**
* Not used for the core, just a sort a hint to get a common setOption() method (for the core and for frontends)
*
* @var array
*/
protected $_specificOptions = array();

/**
* Buffer of backend capabilities
*
* @var array
*/
protected $_backendCapabilities = array();

/**
* Last used cache id
*
* @var string|array
*/
protected $_lastId = '';

/**
* If enableIgnoreUserAbort() succeed and doesn't reset by resetIgnoreUserAbort()
*
* @var boolean
*/
protected $_ignoreUserAbort = false;

/**
* Constructor
*
* @param Zend_Cache_Backend_Interface $backend The backend object
* @param array|Zend_Config $options Frontend and backend options
* @throws Zend_Cache_Exception
*/
public function __construct(Zend_Cache_Backend_Interface $backend, $options = array())
{
$this->setBackend($backend);
$this->setOptions($options);
}

/**
* Create a new instance of the frontend class with same options
* and a reference to the same backend
*
* @return Zend_Cache_Frontend_Core
*/
public function __clone()
{
$className = get_class($this);
return new $className($this->_backend, $this->getOptions());
}

/**
* Get backend instance
*
* @return Zend_Cache_Backend_Interface
*/
public function getBackend()
{
return $this->_backend;
}

/**
* Set backend instance
*
* @param Zend_Cache_Backend_Interface $backend
* @throws Zend_Cache_Exception
*/
public function setBackend(Zend_Cache_Backend_Interface $backend)
{
$capabilities = $backend->getCapabilities();
$this->_backend = $backend;
$this->_backendCapabilities = $capabilities;
}

/**
* Set an option
*
* @param string $name Name of the option
* @param mixed $value Value of the option
* @throws Zend_Cache_Exception
*/
public function setOption($name, $value)
{
$name = strtolower($name);

// handle deprecated options
switch ($name) {
case 'cache_id_prefix':
trigger_error('The option "cache_id_prefix" has been removed. Please use "namespace" instead.');
$name = 'namespace';
break;

case 'automatic_serialization':
trigger_error('The option "automatic_serialization" has been removed. There is no more usage of it.');
return;

case 'automatic_cleaning_factor':
trigger_error('The option "automatic_cleaning_factor" has been removed. Please use "automatic_clearing_factor" instead.');
$name = 'automatic_clearing_factor';
break;

case 'lifetime':
trigger_error('The option "lifetime" has been removed. Please use "ttl" instead.');
$name = 'ttl';
break;
}

// Core options
if (array_key_exists($name, $this->_options)) {
switch ($name) {
case 'caching':
case 'write_control':
case 'ignore_user_abort':
case 'throw_exceptions':
$value = (bool)$value;
break;

case 'tag_namespace':
case 'namespace':
if ($value || $value === '0') {
$value = (string)$value;

if (!$this->_validateWordChar($value)) {
throw new Zend_Cache_Exception('Invalid ' . $name);
}
} else {
$value = null;
}
break;

case 'automatic_clearing_factor':
case 'automatic_optimizing_factor':
$value = (int)$value;
if ($value < 0) {
throw new Zend_Cache_Exception('Invalid '.$name.' "'.$value.'": must be greater or equal 0');
}
break;

case 'ttl':
if (!is_numeric($value)) {
throw new Zend_Cache_Exception('Invalid ttl "'.$value.'": must be numeric');
}
$value = (int)$value;
if ($value < 0) {
throw new Zend_Cache_Exception('Invalid ttl "'.$value.'": must be greater or equal 0');
}
break;

case 'logging':
$value = (bool)$value;
if ($value === true && !$this->_options['logger']) {
// Create a default logger to the standard output stream
require_once 'Zend/Log.php';
$logger = new Zend_Log(new Zend_Log_Writer_Stream('php://output'));
$this->setOption('logger', $logger);
}
break;

case 'logger':
if (!$value) {
if ($this->_options['logging'] === true) {
throw new Zend_Cache_Exception('Can\'t remove logger if logging is enabled. Please disable logging first.');
}
$value = null;
} elseif ( !($value instanceof Zend_Log) ) {
throw new Zend_Cache_Exception('Invalid logger "'.$value.'": must be an instance of Zend_Log');
}
break;

case 'tag_handler':
if (!$value) {
$value = null;
} elseif ( !($value instanceof Zend_Cache_Backend_Interface) ) {
throw new Zend_Cache_Exception('The tag handler must be an instance of Zend_Cache_Backend_Interface');
}
break;
}

$this->_options[$name] = $value;
return;
}

// spezific frontend options
if (array_key_exists($name, $this->_specificOptions)) {
$this->_specificOptions[$name] = $value;
return;
}

// backend options
$this->_backend->setOption($name, $value);
$this->_backendCapabilities = $this->_backend->getCapabilities();
}

/**
* Set options using an associative array or an instance of Zend_Config.
*
* @param array|Zend_Config $options Associative array of options or Zend_Config instance
* @throws Zend_Cache_Exception
*/
public function setOptions($options)
{
if (!($options instanceof Zend_Config) && !is_array($options)) {
throw new Zend_Cache_Exception('Options passed were not an array or Zend_Config instance.');
} elseif ($options instanceof Zend_Config) {
$options = $options->toArray();
}

if ( array_key_exists('logger', $options) && array_key_exists('logging', $options) ) {
if ($options['logging'] && $options['logger']) {
// set logger before enable logging
$this->setOption('logger', $options['logger']);
unset($options['logger']);
} elseif (!$options['logger'] && !$options['logging']) {
// disable logging before removing logger
$this->setOption('logging', $options['logging']);
unset($options['logging']);
}
}

foreach ($options as $name => $value) {
$this->setOption($name, $value);
}
}

/**
* Get an option value
*
* @param string $name Name of the option
* @return mixed option value
* @throws Zend_Cache_Exception
*/
public function getOption($name)
{
if (array_key_exists($name, $this->_options)) {
// This is a Core option
return $this->_options[$name];
} elseif (array_key_exists($name, $this->_specificOptions)) {
// This a specic option of this frontend
return $this->_specificOptions[$name];
} else {
return $this->_backend->getOption($name);
}
}

/**
* Get an array of all options
*
* @return array
*/
public function getOptions()
{
return array_merge($this->_backend->getOptions(), $this->_specificOptions, $this->_options);
}

/**
* Get all backend capabilities
*
* @return array
*/
public function getCapabilities()
{
$capabilities = $this->_backendCapabilities;
return $capabilities;
}

/**
* Get the status.
*
* - total Total space
* - free Free space
*
* @return array
* @throws Zend_Cache_Exception
*/
public function status(array $opts=array())
{
return $this->_backend->status($opts);
}

/**
* Get a cached record (null else).
*
* @param string $id Cache id
* @param array $opts Options (ttl | validate)
* @return mixed Cached content or null
* @throws Zend_Cache_Exception
*/
public function get($id=null, array $opts = array())
{
if (!$this->_options['caching']) {
return null;
}

$id = $this->_id($id);

// default lifetime
if (!isset($opts['ttl'])) {
$opts['ttl'] = $this->_options['ttl'];
}

$this->_normalizeNamespaceOption($opts);

try {
return $this->_backend->get($id, $opts);

} catch (Exception $e) {
$this->_log(__METHOD__ . ' failed: [' . get_class($e) . '] : ' . $e->getMessage());

if ($this->_options['throw_exceptions']) {
throw $e;
}

return null;
}
}

/**
* Get a list of cached records.
*
* @param string[] $idList List of cache ids
* @param array $opts Options
* @return array Cached data in format array(string <CacheId> => mixed <Data>[, ...]).
* If a cache id doesn't hit it is missing in result list.
* @throws Zend_Cache_Exception
*/
public function getMulti(array $idList, array $opts = array()) {
if (!$this->_options['caching']) {
return array();
}

// default lifetime
if (!isset($opts['ttl'])) {
$opts['ttl'] = $this->_options['ttl'];
}

$this->_normalizeNamespaceOption($opts);

try {
return $this->_backend->getMulti($idList, $opts);

} catch (Exception $e) {
$this->_log(__METHOD__ . ' failed: [' . get_class($e) . '] : ' . $e->getMessage());

if ($this->_options['throw_exceptions']) {
throw $e;
}

return array();
}
}

/**
* Issues a request to cache storage for multiple items.
* The method does not wait for response and returns right away.
* When you are ready to collect the items, call either fetch or fetchAll.
*
* Options:
* callback callback The result callback is called for every received item.
* Argument 1: string Cache-Id
* Argument 2: mixed Result
* + all get options
*
* NOTE: If the used cache storage doesn't support this it emulate it by calling getMulti
* and buffering results for fetch and fetchAll calls.
*
* @param array $idList
* @param array $opts
* @return boolean
*/
public function getDelayed(array $idList, array $opts = array()) {
if (!$this->_options['caching']) {
return;
}

// default lifetime
if (!isset($opts['ttl'])) {
$opts['ttl'] = $this->_options['ttl'];
}

$this->_normalizeNamespaceOption($opts);

try {
$this->_backend->getDelayed($idList, $opts);
return true;
} catch (Exception $e) {
$this->_log(__METHOD__ . ' failed: [' . get_class($e) . '] : ' . $e->getMessage());

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Retrieves the next result from the last request of getDelayed.
*
* @return array|boolean Next item as array(<string id> => <mixed data>) or false on failure
*/
public function fetch() {
try {
return $this->_backend->fetch();
} catch (Exception $e) {
$this->_log(__METHOD__ . ' failed: [' . get_class($e) . '] : ' . $e->getMessage());

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Retrieves all the remaining results from the last request of getDelayed.
*
* @return array All results as array([<string id> => <mixed data>[, ...]])
*/
public function fetchAll() {
try {
return $this->_backend->fetchAll();
} catch (Exception $e) {
$this->_log(__METHOD__ . ' failed: [' . get_class($e) . '] : ' . $e->getMessage());

if ($this->_options['throw_exceptions']) {
throw $e;
}

return array();
}
}

/**
* Check if a cache id exists in storage.
*
* @param string $id Cache id
* @param array $opts Options (ttl | validate)
* @return boolean
* @throws Zend_Cache_Exception
* @deprecated
*/
public function exist($id=null, array $opts = array())
{
if (!$this->_options['caching']) {
return false;
}

$id = $this->_id($id);

// default lifetime
if (!isset($opts['ttl'])) {
$opts['ttl'] = $this->_options['ttl'];
}

$this->_normalizeNamespaceOption($opts);

try {
return $this->_backend->exist($id, $opts);
} catch (Exception $e) {
$this->_log(__METHOD__ . ' failed: [' . get_class($e) . '] ' . $e->getMessage());

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Check if a list of cache ids exists in storage.
*
* @param string[] $idList List of cache ids
* @param array $opts Options (ttl | validate)
* @return array Hits in format array(string <CacheId> => TRUE[, ...])
* If a cache id doesn't hit it is missing in result list.
* @throws Zend_Cache_Exception
*/
public function existMulti(array $idList, array $opts = array())
{
if (!$this->_options['caching']) {
return array();
}

// default lifetime
if (!isset($opts['ttl'])) {
$opts['ttl'] = $this->_options['ttl'];
}

$this->_normalizeNamespaceOption($opts);

try {
return $this->_backend->existMulti($idList, $opts);
} catch (Exception $e) {
$this->_log(__METHOD__ . ' failed: [' . get_class($e) . '] ' . $e->getMessage());

if ($this->_options['throw_exceptions']) {
throw $e;
}

return array();
}
}

/**
* Add or replace an cache record.
*
* @param mixed $data Data to put in cache
* @param string $id Cache id (if not set, the last cache id will be used)
* @param array $opts Set options (ttl | tags | priority)
* @return boolean True if no problem
* @throws Zend_Cache_Exception
*/
public function set($data, $id=null, array $opts=array())
{
if (!$this->_options['caching']) {
return false;
}

if (!$data && $data !== '0') {
throw new Zend_Cache_Exception('Missing data');
}

$id = $this->_id($id);
$this->_prepareStoreOptions($opts);
$this->_enableIgnoreUserAbort();

try {
// check tagging by frontend
if ( isset($opts['tags']) && $opts['tags']
&& ($tagHandler = $this->_getTagHandler()) ) {
// if the backend doesn't support tagging
// -> don't send tags array to backend
$frontendTags = $opts['tags'];
if (!$this->_backendCapabilities['tags']) {
unset($opts['tags']);
}
}

$this->_backend->set($data, $id, $opts);

// implement tagging by frontend
if ( isset($frontendTags)) {
$this->_updateTagsByCacheId($id, $frontendTags);
}

$this->_resetIgnoreUserAbort();

// write control
if ($this->_options['write_control']) {
$data2 = $this->_backend->get($id, array('validate' => false,
'namespace' => $opts['namespace']));
if ($data != $data2) {
// don't call backend directly to handle frontend tagging correctly !!!!
$this->remove($id, $opts);

throw new Zend_Cache_Exception('write_control: data corruption of id "' . $id . '"');
}
}

$this->_postpareStore();

return true;

} catch (Exception $e) {
$this->_log(__METHOD__. ' failed: [' . get_class($e) . '] ' . $e->getMessage());

$this->_resetIgnoreUserAbort();

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Add or replace cache records.
*
* @param array $idValueList Array of <string id> => <mixed data> pairs
* @param array $opts Set options (ttl | tags | priority)
* @return boolean True if no problem
* @throws Zend_Cache_Exception
*/
public function setMulti(array $idValueList, array $opts=array())
{
if (!$this->_options['caching']) {
return false;
}

$this->_prepareStoreOptions($opts);
$this->_enableIgnoreUserAbort();

try {
// check tagging by frontend
if ( isset($opts['tags']) && $opts['tags']
&& ($tagHandler = $this->_getTagHandler()) ) {
// if the backend doesn't support tagging
// -> don't send tags array to backend
$frontendTags = $opts['tags'];
if (!$this->_backendCapabilities['tags']) {
unset($opts['tags']);
}
}

$this->_backend->setMulti($idValueList, $opts);

// implement tagging by frontend
if ( isset($frontendTags) ) {
foreach ($idValueList as $id => $data) {
$this->_updateTagsByCacheId($id, $frontendTags);
}
}

$this->_resetIgnoreUserAbort();

// write control
if ($this->_options['write_control']) {
$idValueList2 = $this->_backend->get(array_keys($idValueList), array('validate' => false,
'namespace' => $opts['namespace']));
if ( count(($diff=array_values($idValueList2, $idValueList))) > 0) {
$diffIds = array_keys($diff);
$this->remove($diffIds, $opts);
throw new Zend_Cache_Exception('write_control: data corruption of ids (' . implode(',', $diffIds) . ')');
}
}

$this->_postpareStore();

return true;

} catch (Exception $e) {
$this->_log(__METHOD__. ' failed: [' . get_class($e) . '] ' . $e->getMessage());

$this->_resetIgnoreUserAbort();

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Replace some data of an already cached item.
* This fails if the item doesn't exist.
*
* @param mixed $data Data to put in cache
* @param string $id Cache id (if not set, the last cache id will be used)
* @param array $opts Set options (ttl | tags | priority)
* @return boolean True if no problem
* @throws Zend_Cache_Exception
*/
public function replace($data, $id=null, array $opts=array())
{
if (!$this->_options['caching']) {
return false;
}

if (!$data && $data !== '0') {
throw new Zend_Cache_Exception('Missing data');
}

$id = $this->_id($id);
$this->_prepareStoreOptions($opts);
$this->_enableIgnoreUserAbort();

try {
// check tagging by frontend
if ( isset($opts['tags']) && $opts['tags']
&& ($tagHandler = $this->_getTagHandler()) ) {
// if the backend doesn't support tagging
// -> don't send tags array to backend
$frontendTags = $opts['tags'];
if (!$this->_backendCapabilities['tags']) {
unset($opts['tags']);
}
}

$this->_backend->replace($data, $id, $opts);

// implement tagging by frontend
if ( isset($frontendTags) ) {
$this->_updateTagsByCacheId($id, $frontendTags);
}

$this->_resetIgnoreUserAbort();

// write control
if ($this->_options['write_control']) {
$data2 = $this->_backend->get($id, array('validate' => false,
'namespace' => $opts['namespace']));
if ($data != $data2) {
// don't call backend directly to handle frontend tagging corretly !!!!
$this->remove($id, $opts);

throw new Zend_Cache_Exception('write_control: written and read data doesn\'t match');
}
}

$this->_postpareStore();

return true;

} catch (Exception $e) {
$this->_log(__METHOD__. ' failed: [' . get_class($e) . '] ' . $e->getMessage());

$this->_resetIgnoreUserAbort();

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Replace items of already cached items.
* This fails if an item doesn't exist.
*
* @param array $idValueList Array of <string id> => <mixed data> pairs
* @param array $opts Set options (ttl | tags | priority)
* @return boolean True if no problem
* @throws Zend_Cache_Exception
*/
public function replaceMulti(array $idValueList, array $opts=array())
{
if (!$this->_options['caching']) {
return false;
}

$this->_prepareStoreOptions($opts);
$this->_enableIgnoreUserAbort();

try {
// check tagging by frontend
if ( isset($opts['tags']) && $opts['tags']
&& ($tagHandler = $this->_getTagHandler()) ) {
// if the backend doesn't support tagging
// -> don't send tags array to backend
$frontendTags = $opts['tags'];
if (!$this->_backendCapabilities['tags']) {
unset($opts['tags']);
}
}

$this->_backend->replaceMulti($idValueList, $opts);

// implement tagging by frontend
if ( isset($frontendTags) ) {
foreach ($idValueList as $id => $data) {
$this->_updateTagsByCacheId($id, $frontendTags);
}
}

$this->_resetIgnoreUserAbort();

// write control
if ($this->_options['write_control']) {
$idValueList2 = $this->_backend->get(array_keys($idValueList), array('validate' => false,
'namespace' => $opts['namespace']));
if ( count(($diff=array_values($idValueList2, $idValueList))) > 0) {
$diffIds = array_keys($diff);
$this->remove($diffIds, $opts);
throw new Zend_Cache_Exception('write_control: data corruption of ids (' . implode(',', $diffIds) . ')');
}
}

$this->_postpareStore();

return true;

} catch (Exception $e) {
$this->_log(__METHOD__. ' failed: [' . get_class($e) . '] ' . $e->getMessage());

$this->_resetIgnoreUserAbort();

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Add some data to a new cached item.
* This fails if the item already exist.
*
* @param mixed $data Data to put in cache
* @param string $id Cache id (if not set, the last cache id will be used)
* @param array $opts Set options (ttl | tags | priority)
* @return boolean True if no problem
* @throws Zend_Cache_Exception
*/
public function add($data, $id=null, array $opts=array())
{
if (!$this->_options['caching']) {
return false;
}

if (!$data && $data !== '0') {
throw new Zend_Cache_Exception('Missing data');
}

$id = $this->_id($id);
$this->_prepareStoreOptions($opts);
$this->_enableIgnoreUserAbort();

try {
// check tagging by frontend
if ( isset($opts['tags']) && $opts['tags']
&& ($tagHandler = $this->_getTagHandler()) ) {
// if the backend doesn't support tagging
// -> don't send tags array to backend
$frontendTags = $opts['tags'];
if (!$this->_backendCapabilities['tags']) {
unset($opts['tags']);
}
}

$this->_backend->add($data, $id, $opts);

// implement tagging by frontend
if ( isset($frontendTags) ) {
// TODO: There is no need on reading tags before !
$this->_updateTagsByCacheId($id, $frontendTags);
}

$this->_resetIgnoreUserAbort();

// write control
if ($this->_options['write_control']) {
$data2 = $this->_backend->get($id, array('validate' => false,
'namespace' => $opts['namespace']));
if ($data != $data2) {
// don't call backend directly !!!!
$this->remove($id, $opts);

throw new Zend_Cache_Exception('write_control: written and read data doesn\'t match');
}
}

$this->_postpareStore();

return true;

} catch (Exception $e) {
$this->_log(__METHOD__. ' failed: [' . get_class($e) . '] ' . $e->getMessage());

$this->_resetIgnoreUserAbort();

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}

}

/**
* Add new items.
* This fails if an item already exist.
*
* @param array $idValueList Array of <string id> => <mixed data> pairs
* @param array $opts Set options (ttl | tags | priority)
* @return boolean True if no problem
* @throws Zend_Cache_Exception
*/
public function addMulti(array $idValueList, array $opts=array())
{
if (!$this->_options['caching']) {
return false;
}

$this->_prepareStoreOptions($opts);
$this->_enableIgnoreUserAbort();

try {
// check tagging by frontend
if ( isset($opts['tags']) && $opts['tags']
&& ($tagHandler = $this->_getTagHandler()) ) {
// if the backend doesn't support tagging
// -> don't send tags array to backend
$frontendTags = $opts['tags'];
if (!$this->_backendCapabilities['tags']) {
unset($opts['tags']);
}
}

$this->_backend->addMulti($idValueList, $opts);

// implement tagging by frontend
if ( isset($frontendTags) ) {
// TODO: There is no need on reading tags before !
foreach ($idValueList as $id => $data) {
$this->_updateTagsByCacheId($id, $frontendTags);
}
}

$this->_resetIgnoreUserAbort();

// write control
if ($this->_options['write_control']) {
$idValueList2 = $this->_backend->get(array_keys($idValueList), array('validate' => false,
'namespace' => $opts['namespace']));
if ( count(($diff=array_values($idValueList2, $idValueList))) > 0) {
$diffIds = array_keys($diff);
$this->remove($diffIds, $opts);
throw new Zend_Cache_Exception('write_control: data corruption of ids (' . implode(',', $diffIds) . ')');
}
}

$this->_postpareStore();

return true;

} catch (Exception $e) {
$this->_log(__METHOD__. ' failed: [' . get_class($e) . '] ' . $e->getMessage());

$this->_resetIgnoreUserAbort();

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

protected function _prepareStoreOptions(array &$opts)
{
// default lifetime
if (!isset($opts['ttl'])) {
$opts['ttl'] = $this->_options['ttl'];
}

$this->_normalizeNamespaceOption($opts);

// check & normalize tags array
if (isset($opts['tags']) && $opts['tags']) {
if ( !($opts['tags']=$this->_validateTags($opts['tags'])) ) {
if (is_array($opts['tags'])) {
throw new Zend_Cache_Exception('Invalid tags "'.implode('","', $opts['tags']).'"');
} else {
throw new Zend_Cache_Exception('Invalid tags "'.$opts['tags'].'"');
}
}
}
}

protected function _postpareStore()
{
// automatic clearing
// -> called after storing has finished that new cache ids are available on get for parallel requests
try {
if ($this->_options['automatic_clearing_factor'] > 0) {
$rand = mt_rand(1, $this->_options['automatic_clearing_factor']);
if ($rand==1) {
$this->_backend->clear(Zend_Cache::MATCHING_EXPIRED,
array('ttl' => $this->_options['ttl'],
'namespace' => $this->_options['namespace']) );
}
}
} catch (Zend_Cache_Exception $e) {
$this->_log(__METHOD__. ' automatic clearing failed: [' . get_class($e) . '] ' . $e->getMessage());
// don't throw exception on automatic clearing failed
}

// automatic optimizing
try {
if ($this->_options['automatic_optimizing_factor'] > 0) {
$rand = mt_rand(1, $this->_options['automatic_optimizing_factor']);
if ($rand==1) {
$this->_backend->optimize(array('ttl' => $this->_options['ttl'],
'namespace' => $this->_options['namespace']) );
}
}
} catch (Zend_Cache_Exception $e) {
$this->_log(__METHOD__. ' automatic optimizing failed: [' . get_class($e) . '] ' . $e->getMessage());
// don't throw exception if automatic optimizing failed
}
}

/**
* Remove a cache id
*
* @param string $id Cache id
* @param array $opts Array of options
* @return boolean True if ok
* @throws Zend_Cache_Exception
*/
public function remove($id=null, array $opts=array())
{
if (!$this->_options['caching']) {
return true;
}

$id = $this->_id($id);
$this->_normalizeNamespaceOption($opts);
$this->_enableIgnoreUserAbort();

try {
$this->_backend->remove($id, $opts);

if ( ($tagHandler = $this->_getTagHandler()) ) {
try {
// remove cache id from tag index but do not throw exception on failure
$this->_removeCacheIdFromTagIndex($id);
} catch (Exception $e) {}
}

$this->_resetIgnoreUserAbort();

return true;

} catch (Exception $e) {
$this->_log(__METHOD__ . ' failed: [' . get_class($e) . '] ' . $e->getMessage());

$this->_resetIgnoreUserAbort();

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Remove a list of cache ids
*
* @param string[] $idList List of cache ids
* @param array $opts Array of options
* @return boolean true on success
* @throws Zend_Cache_Exception
*/
public function removeMulti(array $idList, array $opts=array())
{
if (!$this->_options['caching']) {
return true;
}

$this->_normalizeNamespaceOption($opts);
$this->_enableIgnoreUserAbort();

try {
$this->_backend->removeMulti($idList, $opts);

// remove cache id from tag index but do not throw exception on failure
if ( ($tagHandler = $this->_getTagHandler()) ) {
try {
$this->_removeCacheIdFromTagIndexMulti($idList);
} catch (Exception $e) {}
}

$this->_resetIgnoreUserAbort();

return true;

} catch (Exception $e) {
$this->_log(__METHOD__ . ' failed: [' . get_class($e) . '] ' . $e->getMessage());

$this->_resetIgnoreUserAbort();

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Restart lifetime of a cache id.
*
* @param null|string $id Cache id
* @param array $opts Array of options
* @return boolean true on success, false on failure
* @throws Zend_Cache_Exception
* @todo Remove bc for second argument
*/
public function touch($id=null, $opts=array())
{
if (!$this->_options['caching']) {
return true;
}

$id = $this->_id($id);

if ( is_numeric($opts) ) {
trigger_error('The second argument has been changed. Please use the ttl option instead.', E_USER_DEPRECATED);

$newLifetime = (int)$opts;
$opts = array(
'namespace' => $this->_options['namespace']
);

$info = $this->info($id, $opts);
if ($info === false) {
return false;
} elseif (isset($info['mtime'], $info['ttl'])) {
$opts['ttl'] = ($info['mtime'] + $info['ttl'] + $newLifetime) - time();
}
}

// default lifetime
if (!isset($opts['ttl'])) {
$opts['ttl'] = $this->_options['ttl'];
}

$this->_normalizeNamespaceOption($opts);
$this->_enableIgnoreUserAbort();

try {
$rs = $this->_backend->touch($id, $opts);

// touch id2tag-index by tag_handler
if ( ($tagHandler=$this->_getTagHandler()) ) {
$tagHandler->touch(
'id2tag_' . $id,
array('ttl' => 0,
'namespace' => $this->_options['tag_namespace'])
);
}

$this->_resetIgnoreUserAbort();

return true;

} catch (Exception $e) {
$this->_log(__METHOD__ . ' failed: [' . get_class($e) . '] ' . $e->getMessage());

$this->_resetIgnoreUserAbort();

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Restart lifetime of a list of cache ids.
*
* @param string[] $idList List of cache ids
* @param array $opts Array of options
* @return boolean true on success, false on failure
* @throws Zend_Cache_Exception
*/
public function touchMulti(array $idList, array $opts=array())
{
if (!$this->_options['caching']) {
return true;
}

// default lifetime
if (!isset($opts['ttl'])) {
$opts['ttl'] = $this->_options['ttl'];
}

$this->_normalizeNamespaceOption($opts);
$this->_enableIgnoreUserAbort();

try {
$rs = $this->_backend->touchMulti($idList, $opts);

// touch id2tag-index by tag_handler
if ( ($tagHandler=$this->_getTagHandler()) ) {
$tagTouchIdList = array();
foreach ($idList as $idSingle) {
$tagTouchId[] = 'id2tag_' . $idSingle;
}

$tagHandler->touchMulti(
$tagTouchIdList,
array('ttl' => 0,
'namespace' => $this->_options['tag_namespace'])
);
}

$this->_resetIgnoreUserAbort();

return true;

} catch (Exception $e) {
$this->_log(__METHOD__ . ' failed: [' . get_class($e) . '] ' . $e->getMessage());

$this->_resetIgnoreUserAbort();

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Increment numeric item's value
*
* @param int|float $value The amount by which to increment the item's value
* @param null|string $id Cache Id
* @param array $opts Options
* @return boolean
* @throws Zend_Cache_Exception
*/
public function increment($value=1, $id=null, array $opts=array()) {
if (!$this->_options['caching']) {
return true;
}

if (($value=(float)$value) < 0) {
throw new Zend_Cache_Exception('Can\'t increment negativ numbers');
}

$id = $this->_id($id);

// default lifetime
if (!isset($opts['ttl'])) {
$opts['ttl'] = $this->_options['ttl'];
}

$this->_normalizeNamespaceOption($opts);
$this->_enableIgnoreUserAbort();

try {
$this->_backend->increment($value, $id, $opts);

$this->_resetIgnoreUserAbort();

return true;
} catch (Exception $e) {
$this->_log(__METHOD__ . ' failed: [' . get_class($e) . '] ' . $e->getMessage());

$this->_resetIgnoreUserAbort();

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Increment a list of numeric item's values.
*
* @param array $idValueList List of cache ids and values, format: array(string <id> => int <value>[, ...])
* @param array $opts Options
* @return boolean
* @throws Zend_Cache_Exception
*/
public function incrementMulti(array $idValueList, array $opts=array()) {
if (!$this->_options['caching']) {
return true;
}

if (($value=(int)$value) < 0) {
throw new Zend_Cache_Exception('Can\'t increment negativ numbers');
}

// default lifetime
if (!isset($opts['ttl'])) {
$opts['ttl'] = $this->_options['ttl'];
}

$this->_normalizeNamespaceOption($opts);
$this->_enableIgnoreUserAbort();

try {
$this->_backend->incrementMulti($idValueList, $value, $opts);

$this->_resetIgnoreUserAbort();

return true;
} catch (Exception $e) {
$this->_log(__METHOD__ . ' failed: [' . get_class($e) . '] ' . $e->getMessage());

$this->_resetIgnoreUserAbort();

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Decrement numeric item's value
*
* @param int|float $value The amount by which to decrement the item's value
* @param null|string $id Cache id
* @param array $opts Options
* @return boolean
* @throws Zend_Cache_Exception
*/
public function decrement($value=1, $id=null, array $opts=array()) {
if (!$this->_options['caching']) {
return true;
}

if (($value=(float)$value) < 0) {
throw new Zend_Cache_Exception('Can\'t increment negativ numbers');
}

$id = $this->_id($id);

// default lifetime
if (!isset($opts['ttl'])) {
$opts['ttl'] = $this->_options['ttl'];
}

$this->_normalizeNamespaceOption($opts);
$this->_enableIgnoreUserAbort();

try {
$this->_backend->decrement($value, $id, $opts);

$this->_resetIgnoreUserAbort();

return true;
} catch (Exception $e) {
$this->_log(__METHOD__ . ' failed: [' . get_class($e) . '] ' . $e->getMessage());

$this->_resetIgnoreUserAbort();

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Decrement a list of numeric item's values.
*
* @param array $idValueList List of cache ids and values, format: array(string <id> => int <value>[, ...])
* @param array $opts Options
* @return boolean
* @throws Zend_Cache_Exception
*/
public function decrementMulti(array $idValueList, array $opts=array()) {
if (!$this->_options['caching']) {
return true;
}

// default lifetime
if (!isset($opts['ttl'])) {
$opts['ttl'] = $this->_options['ttl'];
}

$this->_normalizeNamespaceOption($opts);
$this->_enableIgnoreUserAbort();

try {
$this->_backend->decrementMulti($idValueList, $opts);

$this->_resetIgnoreUserAbort();

return true;
} catch (Exception $e) {
$this->_log(__METHOD__ . ' failed: [' . get_class($e) . '] ' . $e->getMessage());

$this->_resetIgnoreUserAbort();

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Get an array of information for a cached item
*
* Info-Keys:
* mtime int Last modification time
* tags array All assigned tags
* ttl int Time to life
* + available backend spezific information
*
* @param string $id Cache id
* @param array $opts Array of options
* @return array|boolean array of informations or false if not exist
* @throws Zend_Cache_Exception
*/
public function info($id=null, array $opts=array())
{
if (!$this->_options['caching']) {
return false;
}

$id = $this->_id($id);

// default lifetime
if (!isset($opts['ttl'])) {
$opts['ttl'] = $this->_options['ttl'];
}

$this->_normalizeNamespaceOption($opts);

try {
$info = $this->_backend->info($id, $opts);

// add tag array by frontend if backend doesn't support tagging
if ($info !== false && ($tagHandler=$this->_getTagHandler())) {

$tagHandlerGetAndSetOpts = array(
'ttl' => 0,
'namespace' => $this->_options['tag_namespace'],
'validate' => false,
);

$id2tagId = 'id2tag_' . $id;
$tags = $tagHandler->get($id2tagId, $tagHandlerGetAndSetOpts);
if (is_array($tags)) {
$info['tags'] = $tags;
}
}

return $info;

} catch (Exception $e) {
$this->_log(__METHOD__ . ' failed: [' . get_class($e) . '] ' . $e->getMessage());

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Get a list of array information for cached items
*
* Info-Keys:
* mtime int Last modification time
* tags array All assigned tags
* ttl int Time to life
* + available backend spezific information
*
* @param string[] $idList List of cache ids
* @param array $opts Array of options
* @return array List of information array, format: array(string <id> => array(string <key> => mixed <value>)[, ...])
* @throws Zend_Cache_Exception
*/
public function infoMulti(array $idList, array $opts=array())
{
if (!$this->_options['caching']) {
return array();
}

// default lifetime
if (!isset($opts['ttl'])) {
$opts['ttl'] = $this->_options['ttl'];
}

$this->_normalizeNamespaceOption($opts);

try {
$infoList = $this->_backend->infoMulti($idList, $opts);

// add tag array by frontend tagging
if ( ($tagHandler=$this->_getTagHandler()) ) {

$tagHandlerGetAndSetOpts = array(
'ttl' => 0,
'namespace' => $this->_options['tag_namespace'],
'validate' => false,
);

$id2tagIdList = array();
foreach ($infoList as $id => $info) {
$id2tagIdList[$id] = 'id2tag_' . $id;
}
$id2tagRsList = $tagHandler->getMulti($id2tagIdList, $tagHandlerGetAndSetOpts);
foreach ($id2tagRsList as $id2tagId => $tags) {
if (is_array($tags) && ($id=array_search($id2tagId, $id2tagIdList))) {
$infoList[$id]['tags'] = $tags;
}
}
}

return $infoList;

} catch (Exception $e) {
$this->_log(__METHOD__ . ' failed: [' . get_class($e) . '] ' . $e->getMessage());

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Clear cache by matching mode
* The value of flags can be any combination of Zend_Cache::MATCHING_* constants (with some restrictions), joined with the binary OR (|) operator.
*
* @param int $mode Matching mode
* @param array $opts Array of options
* @return boolean true on success and false on failure
* @throws Zend_Cache_Exception
*/
public function clear($mode = Zend_Cache::MATCHING_ALL, array $opts=array())
{
try {
// handle matching mode restrictions
if (($normalizedMode=$this->_validateMatchingMode($mode)) === false) {
throw new Zend_Cache_Exception('Invalid matching mode given, "'.$mode.'"');
}

// TODO: bc
if ( ($normalizedMode & 070) > 0
&& (!isset($opts['tags']) || !($opts['tags']=$this->_validateTags($opts['tags']))) ) {
throw new Zend_Cache_Exception('Missing or invalid tags option');
}

// default lifetime
if (!isset($opts['ttl'])) {
$opts['ttl'] = $this->_options['ttl'];
}

$this->_normalizeNamespaceOption($opts);

// frontend tagging
if ( ($normalizedMode & 070) > 0 && $this->_getTagHandler() ) {
// Don't call backend directly
$ids = $this->find($normalizedMode, $opts);
$this->removeMulti($ids);
} else {
$this->_backend->clear($normalizedMode, $opts);
}

return true;

} catch (Exception $e) {
$this->_log(__METHOD__ , ' failed: [' . get_class($e) . '] ' . $e->getMessage());

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Find stored cache ids by matching mode and options
*
* @param int $mode Matching mode
* @param array $opts Array of options
* @return string[]|boolean List of stored cache ids or false on error
* @throws Zend_Cache_Exception
*/
public function find($mode = Zend_Cache::MATCHING_ACTIVE, array $opts=array())
{
try {
// handle matching mode restrictions
if (($normalizedMode=$this->_validateMatchingMode($mode)) === false) {
throw new Zend_Cache_Exception('Invalid matching mode given, "'.$mode.'"');
}

if ( ($normalizedMode & 070) > 0
&& (!isset($opts['tags']) || !($opts['tags']=$this->_validateTags($opts['tags']))) ) {
throw new Zend_Cache_Exception('Missing or invalid tags option');
}

// default lifetime
if (!isset($opts['ttl'])) {
$opts['ttl'] = $this->_options['ttl'];
}

$this->_normalizeNamespaceOption($opts);

$ids = array();

// frontend tagging
if ( ($normalizedMode & 070) > 0
&& ($tagHandler = $this->_getTagHandler()) ) {

$tagHandlerGetAndSetOpts = array(
'ttl' => 0,
'namespace' => $this->_options['tag_namespace'],
'validate' => false,
);

// if MATCHING_TAGS mode -> get all cache ids where all given tags match
if (($normalizedMode & Zend_Cache::MATCHING_TAGS) == Zend_Cache::MATCHING_TAGS ) {
// get only the first tag and than check the loaded cache ids by metadata
$tag = array_shift($opts['tags']);
$tagid = 'tag2id_'.$tag;
$tagCacheIds = $tagHandler->get($tagid, $tagHandlerGetAndSetOpts);
if ($tagCacheIds) {
foreach ($tagCacheIds as $id => $tmp) {
$tagCacheId = 'id2tag_'.$id;
$tags = $tagHandler->get($tagCacheId, $tagHandlerGetAndSetOpts);
if ($tags && count(array_diff($opts['tags'], $tags)) == 0) {
$ids[] = $id;
}
}
}

// if MATCHING_ANY_TAGS mode -> get all cache ids of given tags
} elseif ( ($normalizedMode & Zend_Cache::MATCHING_ANY_TAGS) == Zend_Cache::MATCHING_ANY_TAGS ) {
foreach ($opts['tags'] as $tag) {
$tagid = 'tag2id_'.$tag;
$tagCacheIds = $tagHandler->get($tagid, $tagHandlerGetAndSetOpts);
if (!$tagCacheIds) {
continue;
}
$ids+= $tagCacheIds;
}

// unique ids
$ids = array_keys($ids);

// if MATCHING_NO_TAGS mode -> check if no given tag available in current cache
} elseif( ($normalizedMode & Zend_Cache::MATCHING_NO_TAGS) == Zend_Cache::MATCHING_NO_TAGS ) {
if (!$this->_backendCapabilities['list']) {
throw new Zend_Cache_Exception('Needs "list"-functionality to get ids by frontend with mode Zend_Cache::MATCHING_NO_TAGS');
}

// get cache ids without tag filter
$subMode = $normalizedMode & Zend_Cache::MATCHING_ALL;
$subOpts = array('validate' => false) + $opts;
unset($subOpts['tags']);

// TODO: use getMulti
foreach ($this->_backend->find($subMode, $subOpts) as $id) {
$tagCacheId = 'id2tag_'.$id;
$tags = $tagHandler->get($tagCacheId, $tagHandlerGetAndSetOpts);
if (count(array_diff($opts['tags'], $tags)) == count($opts['tags'])) {
$ids[] = $id;
}
}
}

// Filter expired/active
if ( ($mode & Zend_Cache::MATCHING_ALL) != Zend_Cache::MATCHING_ALL
&& ($mode & Zend_Cache::MATCHING_ALL) != 0 ) {
if (!isset($opts['ttl'])) {
$ttl = $this->_options['ttl'];
} else {
$ttl = (int)$opts['ttl'];
}
$now = time();
foreach ($ids as $i => $id) {
$info = $this->_backend->info($id, array('validate' => false) + $opts);
if ($info !== false) {

// if Zend_Cache::MATCHING_EXPIRED mode selected do not remove active data
if (($normalizedMode & Zend_Cache::MATCHING_EXPIRED) == Zend_Cache::MATCHING_EXPIRED) {
if ( $now <= ($info['mtime']+$ttl) ) {
unset($ids[$i]);
}

// if Zend_Cache::MATCHING_ACTIVE mode selected do not remove expired data
} else {
if ( $now > ($info['mtime']+$$ttl) ) {
$ids[$i];
}
}
} else {
unset($ids[$i]);
}
}
}

// get ids by backend
} else {
$ids = $this->_backend->find($normalizedMode, $opts);
}

return $ids;

} catch (Exception $e) {
$this->_log(__METHOD__.' failed: [' . get_class($e) . '] ' . $e->getMessage());

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Optimize cache data structure.
*
* @param array $opts
* @return boolean true on success, false on failure
* @throws Zend_Cache_Exception
*/
public function optimize(array $opts = array())
{
// default lifetime
if (!isset($opts['ttl'])) {
$opts['ttl'] = $this->_options['ttl'];
}

$this->_normalizeNamespaceOption();
$this->_enableIgnoreUserAbort();

try {
$this->_backend->optimize($opts);

$this->_resetIgnoreUserAbort();

return true;
} catch (Zend_Cache_Exception $e) {
$this->_log(__METHOD__ . ' failed: [' . get_class($e) . '] ' . $e->getMessage());

$this->_resetIgnoreUserAbort();

if ($this->_options['throw_exceptions']) {
throw $e;
}

return false;
}
}

/**
* Log a message by options.
*
* @param string $message
* @param int $priority
* @throws Zend_Cache_Exception
*/
protected function _log($message, $priority = 4)
{
if (!$this->_options['logging']) {
// logging disabled
return;
}

$this->_options['logger']->log($message, $priority);
}

/**
* Normalize, check, buffer and return a cache id
*
* @param string $id Cache id
* @return string Normalized cache id
* @throws Zend_Cache_Exception
*/
protected function _id($id)
{
if ( ($id=(string)$id) === '' ) {
if ($this->_lastId === '') {
throw new Zend_Cache_Exception('Missing id');
}
} else {
// update last id
$this->_lastId = $id;
}

return $this->_lastId;
}

/**
* Normalize namespace option
*
* @param array $opts Options
*/
protected function _normalizeNamespaceOption(array &$opts)
{
if ( isset($opts['namespace'])
&& !$this->_validateWordChar($opts['namespace']) ) {
throw new Zend_Cache_Exception('Invalid namespace "' . $opts['namespace'] . '"');
} else {
// set default namespace
$opts['namespace'] = $this->_options['namespace'];
}
}

/**
* Helper method to get the tag handler if it is defined or
* if the backend doesn't support tagging
*
* @return Zend_Cache_Backend_Interface|null
*/
protected function _getTagHandler()
{
if ($this->_options['tag_handler']) {
return $this->_options['tag_handler'];
} elseif (!$this->_backendCapabilities['tags']) {
return $this->_backend;
}

return null;
}

/**
* Helper method of frontend tagging to remove a list of cache ids from tag index.
*
* @param string $ids List of cache ids
* @throws Zend_Cache_Exception
*/
protected function _removeCacheIdFromTagIndexMulti(array $ids) {
foreach ($ids as $idSingle) {
$this->_removeCacheIdFromTagIndex($idSingle);
}
}

/**
* Helper method of frontend tagging to remove a single cache id from tag index.
*
* @param string $id The cache id
* @throws Zend_Cache_Exception
*/
protected function _removeCacheIdFromTagIndex($id)
{
$tagHandler = $this->_getTagHandler();
$getAndSetOpts = array(
'ttl' => 0,
'namespace' => $this->_options['tag_namespace'],
'validate' => false,
);

// remove list
$deleteList = array();

// replace data
$replaceList = array();

// get all tag indexes of cache id
$rs = $tagHandler->get('id2tag_' . $id, $getAndSetOpts);
$deleteList[] = 'id2tag_' . $id;

if (is_array($rs)) {
// remove cache id from all tag indexes
$tagIdList = array();
foreach ($rs as $tag) {
$tagIdList[] = 'tag2id_' . $tag;
}
$tagIndexList = $tagHandler->getMulti($tagIdList, $getAndSetOpts);

$removeTagIndexList = array();
foreach ($tagIndexList as $tagIndexId => $tagCacheIds) {
if (isset($tagCacheIds[$id])) {
unset($tagCacheIds[$id]);
if ($tagCacheIds) {
$replaceList[$tagIndexId] = $tagCacheIds;
} else {
// mark empty tag index for delete
$deleteList[] = $tagIndexId;
}
}
}
}

// remove all cache ids marked for delete
$tagHandler->removeMulti($deleteList, $getAndSetOpts);

// replace data
$tagHandler->replaceMulti($replaceList, $getAndSetOpts);
}

/**
* Helper method of frontend tagging to update tags of the given cache id.
*
* @param string $id The cache id
* @param string[] $tags List of tags
* @throws Zend_Cache_Exception
*/
protected function _updateTagsByCacheId($id, array $tags)
{
$tagHandler = $this->_getTagHandler();
$getAndSetOpts = array(
'ttl' => 0,
'namespace' => $this->_options['tag_namespace'],
'validate' => false,
);

// get all tag indexes
$tagIdList = array();
foreach ($tags as $tag) {
$tagIdList[] = 'tag2id_' . $tag;
}
$tagIndexList = $tagHandler->getMulti($tagIdList, $getAndSetOpts);

// add cache id to existing tag indexs
$data = null;
foreach ($tagIndexList as $tagId => $tagCacheIds) {
// save reference only if cache id not exist in that index
if (!isset($tagCacheIds[$id])) {
$data[$tagId] = $tagCacheIds;
$data[$tagId][$id] = true;
}
}
if ($data !== null) {
$tagHandler->replaceMulti($data, $getAndSetOpts);
}

// add missing tag indexes
$dataOnMissing = array($id => true);
$tagIndexListMissing = array_diff($tagIdList, array_keys($tagIndexList));
$data = null;
foreach ($tagIndexListMissing as $tagId) {
$data[$tagId] = $dataOnMissing;
}
if ($data !== null) {
$tagHandler->addMulti($data, $getAndSetOpts);
}

// save reference: cache id -> tag(s)
$tagHandler->set($tags, 'id2tag_' . $id, $getAndSetOpts);
}

/**
* Validate (and normalize) matching mode.
*
* @param int $mode The matching mode
* @return int|boolean A normalized matching mode or false on invalid matching mode
*/
protected function _validateMatchingMode($mode)
{
$mode = (int)$mode;

// the matching mode can't handle more than one tag matching
if ( ($mode & Zend_Cache::MATCHING_TAGS) == Zend_Cache::MATCHING_TAGS
&& ($mode & Zend_Cache::MATCHING_NO_TAGS) == Zend_Cache::MATCHING_NO_TAGS ) {
return false;
} elseif (($mode & Zend_Cache::MATCHING_ANY_TAGS) == Zend_Cache::MATCHING_ANY_TAGS
&& ($mode & Zend_Cache::MATCHING_NO_TAGS) == Zend_Cache::MATCHING_NO_TAGS ) {
return false;
}

// set self::MATCHING_ALL as default
if ( ($mode & Zend_Cache::MATCHING_ACTIVE) != Zend_Cache::MATCHING_ACTIVE
&& ($mode & Zend_Cache::MATCHING_EXPIRED) != Zend_Cache::MATCHING_EXPIRED ) {
$mode = $mode | Zend_Cache::MATCHING_ALL;
}

return $mode;
}

/**
* Validate (and normalize) tags array
*
* @param mixed $tags Tags to validate
* @return array|boolean Normalized tags array, false else
*/
protected function _validateTags($tags)
{
if (!is_array($tags)) {
$tags = array($tags);
}

$normalizedTags = array();
foreach ($tags as $k => $tag) {
$tag = (string)$tag;
if ($tag !== '') {
if (!$this->_validateWordChar($tag)) {
return false;
}

$normalizedTags[$tag] = null;
}
}

return array_keys($normalizedTags);
}

/**
* Validate if $value only contains word characters (a-z,A-Z,0-9,_).
*
* @param string $value
* @return boolean TRUE if valid | FALSE if invalid
*/
protected function _validateWordChar($value)
{
return (bool)preg_match('/^[\w]+$/D', $value);
}

/**
* Enable ignore user abort if it is disabled
* and only if option ignore_user_abort is enabled
*/
protected function _enableIgnoreUserAbort()
{
if ($this->_options['ignore_user_abort']) {
$this->_ignoreUserAbort = !ignore_user_abort(true);
} else {
$this->_ignoreUserAbort = false;
}
}

/**
* Redisable ignore user abort if it was enabled by setIgnoreUserAbort before
* and exit script if connection aborted.
*/
protected function _resetIgnoreUserAbort()
{
if ($this->_ignoreUserAbort) {
ignore_user_abort(false);
if (connection_aborted()) {
exit;
}
$this->_ignoreUserAbort = false;
}
}

/* deprecated methods */

/**
* @deprecated Please set the option "ttl" instead
*/
public function setLifetime($newLifetime)
{
trigger_error('This method is deprecated and will be removed in a future release. Please use the option "ttl" instead.', E_USER_DEPRECATED);
$this->setOption('ttl', $newLifetime);
}

/**
* @deprecated Please use get instead.
*/
public function load($id, $doNotTestCacheValidity = false, $doNotUnserialize = false)
{
trigger_error('This method is deprecated and will be removed in a future release. Please use get instead.', E_USER_DEPRECATED);

$opts = array();

if ($doNotTestCacheValidity) {
$opts['validate'] = false;
}

if ($doNotUnserialize) {
throw new Zend_Cache_Exception('Serialization is no more available on frontend.');
}

return $this->get($id, $opts);
}

/**
* Test if a cache is available for the given id.
*
* @param string $id Cache id
* @param array $opts Cache test options (ttl | validate)
* @return int|boolean Last modification time, false else
* @throws Zend_Cache_Exception
* @deprecated
*/
public function test($id=null)
{
trigger_error('This method is deprecated and will be removed in a future release. Please use exist or info instead.', E_USER_DEPRECATED);

$info = $this->info($id, $opts);
return isset($info['mtime']) ? $info['mtime'] : false;
}

/**
* Save some data in a cache
*
* @param mixed $data Data to put in cache (can be another type than string if automatic_serialization is on)
* @param string $id Cache id (if not set, the last cache id will be used)
* @param array $tags Cache tags
* @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
* @param int $priority integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends
* @throws Zend_Cache_Exception
* @return boolean True if no problem
* @deprecated Please use set instead.
*/
public function save($data, $id = null, $tags = array(), $specificLifetime = false, $priority = 8)
{
trigger_error('This method is deprecated and will be removed in a future release. Please use set/add/replace instead.', E_USER_DEPRECATED);

return $this->set($data, $id, array(
'tags' => $tags,
'ttl' => $specificLifetime,
'priority' => $priority
));
}

/**
* @deprecated Please use clear instead
*/
public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, array $tags=array()) {
trigger_error('This method is deprecated and will be removed in a future release. Please use clear instead.', E_USER_DEPRECATED);

switch ($mode) {
case Zend_Cache::CLEANING_MODE_ALL:
$mode = Zend_Cache::MATCHING_ALL;
break;
case Zend_Cache::CLEANING_MODE_OLD:
$mode = Zend_Cache::MATCHING_EXPIRED;
break;
case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
$mode = Zend_Cache::MATCHING_TAGS;
break;
case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
$mode = Zend_Cache::MATCHING_ANY_TAGS;
break;
case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
$mode = Zend_Cache::MATCHING_NO_TAGS;
break;
default:
throw new Zend_Cache_Exception('Unknown cleaning mode given "'.$mode.'"');
}

return $this->clear($mode, array('tags' => $tags));
}

/**
* Return an array of stored cache ids which match given tags
*
* In case of multiple tags, a logical AND is made between tags
*
* @param array $tags array of tags
* @return array array of matching cache ids (string)
* @deprecated Please use find instead
*/
public function getIdsMatchingTags($tags = array())
{
trigger_error('This method is deprecated and will be removed in a future release. Please use find instead.', E_USER_DEPRECATED);
return $this->find(Zend_Cache::MATCHING_ALL | Zend_Cache::MATCHING_TAGS, array('tags' => $tags));
}

/**
* Return an array of stored cache ids which don't match given tags
*
* In case of multiple tags, a logical OR is made between tags
*
* @param array $tags array of tags
* @return array array of not matching cache ids (string)
* @deprecated Please use find instead
*/
public function getIdsNotMatchingTags($tags = array())
{
trigger_error('This method is deprecated and will be removed in a future release. Please use find instead.', E_USER_DEPRECATED);
return $this->find(Zend_Cache::MATCHING_ALL | Zend_Cache::MATCHING_ANY_TAGS, array('tags' => $tags));
}

/**
* Return an array of stored cache ids
*
* @return array array of stored cache ids (string)
* @throws Zend_Cache_Exception
* @deprecated Please use find instead
*/
public function getIds()
{
trigger_error('This method is deprecated and will be removed in a future release. Please use find instead.', E_USER_DEPRECATED);
return $this->find(Zend_Cache::MATCHING_ALL);
}

/**
* Return an array of stored tags
*
* @return array array of stored tags (string)
* @deprecated This method will be removed in a future relaese.
*/
public function getTags()
{
trigger_error('This method will be removed in a future relaese.', E_USER_DEPRECATED);

$tags = array();
$idList = $this->find(Zend_Cache::MATCHING_ACTIVE);
$infoList = $this->infoMulti($idList, array('validate' => false));
foreach ($infoList as $id => $info) {
if (isset($info['tags'])) {
$tags = array_merge($tags, $info['tags']);
}
}

return array_unique($tags);
}

/**
* Return the filling percentage of the backend storage
*
* @return int integer between 0 and 100
* @deprecated Please use status instead
*/
public function getFillingPercentage()
{
trigger_error('This method is deprecated and will be removed in a future release. Please use status instead.', E_USER_DEPRECATED);

$storageStatus = $this->status();
$used = $storageStatus['total'] - $storageStatus['free'];
return (int)(100. * ($used / $storageStatus['total']));
}

/**
* Return an array of metadatas for the given cache id
*
* The array will include these keys :
* - expire : the expire timestamp
* - tags : a string array of tags
* - mtime : timestamp of last modification time
*
* @param string $id cache id
* @return array array of metadatas (false if the cache id is not found)
* @deprecated Please use info instead.
*/
public function getMetadatas($id)
{
trigger_error('This method is deprecated and will be removed in a future release. Please use info instead.', E_USER_DEPRECATED);

$info = $this->info($id);
if ($info !== false) {
return array(
'expire' => (isset($info['ttl'], $info['mtime']) ? $info['mtime']+$info['ttl'] : 0),
'tags' => (isset($info['tags']) ? $info['tags'] : array()),
'mtime' => (isset($info['mtime']) ? $info['mtime'] : 0)
);
}

return false;
}

}
{code}
{card}


{card:label=Zend_Cache_Backend_Interface}
{code}
interface Zend_Cache_Backend_Interface
{

/**
* Get option
*
* @param string $name
* @return mixed
*/
public function getOption($name);

/**
* Get an associative array of all options.
*
* @return array
*/
public function getOptions();

/**
* Set option
*
* @param string $name
* @param mixed $value
* @throws Zend_Cache_Exception
*/
public function setOption($name, $value);

/**
* Set options using an associative array or an instance of Zend_Config.
*
* @param array|Zend_Config $options Associative array of options or Zend_Config instance
* @throws Zend_Cache_Exception
*/
public function setOptions($options);

/**
* Get capabilities of this backend
*
* - tags bool Is tagging implemented
* - clear_tags bool Is clearing of tags supported
* - expired_read bool Are expired but cache records readable
* - priority bool Are the cache data priorizable
* - lifetime_infinite bool Is the lifetime infinite
* - lifetime_static bool Is the lifetime static
* - list bool Can the stored cache ids listed
*
* @return array
*/
public function getCapabilities();

/**
* Get status information.
*
* Options:
* - extra boolean Add extra status, default: false
*
* Response:
* - total int|float Total space
* - free int|float Free space
* - extra array Extra information
*
* NOTE: If no status can detected by backend the values of total and free are zero.
* NOTE: The extra status is only available if called with option extra to true.
*
* @param $opts Options
* @return array
* @throws Zend_Cache_Exception
*/
public function status(array $opts=array());

/**
* Get a cached record (null else).
*
* @param string $id Cache id
* @param array $opts Options
* @return mixed Cached data or null if cache doesn't exist
* @throws Zend_Cache_Exception
*/
public function get($id, array $opts = array());

/**
* Get a list of cached records.
*
* @param atring[] $id List of cache ids
* @param array $opts Options
* @return array Cached data in format array(string <CacheId> => mixed <Data>[, ...])
* If a cache id doesn't hit it is missing in result list.
* @throws Zend_Cache_Exception
*/
public function getMulti(array $idList, array $opts = array());

/**
* Issues a request to cache storage for multiple items.
* The method does not wait for response and returns right away.
* When you are ready to collect the items, call either fetch or fetchAll.
*
* Options:
* callback callback The result callback is called for every received item.
* Argument 1: string Cache-Id
* Argument 2: mixed Result
* + all get options
*
* NOTE: If the used cache storage doesn't support this it emulate it by calling getMulti
* and buffering results for fetch and fetchAll calls.
*
* @param array $idList
* @param array $opts
*/
public function getDelayed(array $idList, array $opts = array());

/**
* Retrieves the next result from the last request of getDelayed.
*
* @return array|boolean Next item as array(<string id> => <mixed data>) or false on failure
*/
public function fetch();

/**
* Retrieves all the remaining results from the last request of getDelayed.
*
* @return array All results as array([<string id> => <mixed data>[, ...]])
*/
public function fetchAll();

/**
* Check if a cache id exists in storage.
*
* @param string $id Cache id
* @param array $opts Options
* @return boolean
* @throws Zend_Cache_Exception
*/
public function exist($id, array $opts = array());

/**
* Check if a list of cache ids exists in storage.
*
* @param string[] $idList List of cache ids
* @param array $opts Options
* @return array Hits in format array(string <CacheId> => TRUE[, ...])
* If a cache id doesn't hit it is missing in result list.
* @throws Zend_Cache_Exception
*/
public function existMulti(array $idList, array $opts = array());

/**
* Add or replace a cache record.
*
* @param mixed $data Cache content
* @param string $id Cache id
* @param array $opts Options
* @throws Zend_Cache_Exception
*/
public function set($data, $id, array $opts = array());

/**
* Add or replace cache records.
*
* @param array $idValueList Array of <string id> => <mixed value> pairs
* @param array $opts Options
* @throws Zend_Cache_Exception
*/
public function setMulti(array $idValueList, array $opts=array());

/**
* Replace a cache record under an existing id.
* This fails if the item doesn't exist.
*
* @param mixed $data Cache content
* @param string $id Cache id
* @param array $opts Options
* @throws Zend_Cache_Exception
*/
public function replace($data, $id, array $opts = array());

/**
* Replace cache records.
* This fails if the item doesn\'t exist.
*
* @param array $idValueList Array of <string id> => <mixed value> pairs
* @param array $opts Options
* @throws Zend_Cache_Exception
*/
public function replaceMulti(array $idValueList, array $opts=array());

/**
* Create a new cache record.
* This fails if the cache id already exist.
*
* @param mixed $data Cache content
* @param string $id Cache id
* @param array $opts Options
* @throws Zend_Cache_Exception
*/
public function add($data, $id, array $opts = array());

/**
* Create new cache records.
* This fails if the cache id already exist.
*
* @param array $idValueList Array of <string id> => <mixed value> pairs
* @param array $opts Options
* @throws Zend_Cache_Exception
*/
public function addMulti(array $idValueList, array $opts=array());

/**
* Remove a cache record.
*
* @param string $id Cache id
* @param array $opts Options
* @throws Zend_Cache_Exception
*/
public function remove($id, array $opts = array());

/**
* Remove a list of cache records.
*
* @param string[] $idList List of cache ids
* @param array $opts Options
* @throws Zend_Cache_Exception
*/
public function removeMulti(array $idList, array $opts = array());

/**
* Restart lifetime countdown of a cache record.
*
* @param string $id Cache id
* @param array $opts Options
* @throws Zend_Cache_Exception
*/
public function touch($id, array $opts = array());

/**
* Restart lifetime countdown of a list of cache records.
*
* @param string[] $idList List of cache ids
* @param array $opts Options
* @throws Zend_Cache_Exception
*/
public function touchMulti(array $idList, array $opts = array());

/**
* Increment numeric item's value
*
* @param int|float $value The amount by which to increment the item's value
* @param string $id Cache id
* @param array $opts Options
* @throws Zend_Cache_Exception
*/
public function increment($value, $id, array $opts=array());

/**
* Increment list of numeric item's values.
*
* @param array $idValueList List of cache ids and values, format: array(string <id> => int <value>[, ...])
* @param array $opts Options
* @throws Zend_Cache_Exception
*/
public function incrementMulti(array $idValueList, array $opts=array());

/**
* Decrement numeric item's value.
*
* @param int|float $value The amount by which to decrement the item's value
* @param string $id Cache id
* @param array $opts Options
* @throws Zend_Cache_Exception
*/
public function decrement($value, $id, array $opts=array());

/**
* Decrement a list of numeric item's values.
*
* @param array $idValueList List of cache ids and values, format: array(string <id> => int <value>[, ...])
* @param array $opts Options
* @throws Zend_Cache_Exception
*/
public function decrementMulti(array $idValueList, array $opts=array());

/**
* Get an array of information of a cached item
*
* @param string $id Cache-Id
* @param array $opts Options
* @return array|boolean An information array or false if cache doesn't hit
* @throws Zend_Cache_Exception
*/
public function info($id, array $opts = array());

/**
* Get a list of information arrays of a cached items
*
* @param string[] $idList List of cache ids
* @param array $opts Options
* @return array A list of information arrays
* @throws Zend_Cache_Exception
*/
public function infoMulti(array $idList, array $opts = array());

/**
* Clear cached records by matching mode and options.
*
* @param string $mode matching mode
* @param array $opts Array of options (tags)
* @throws Zend_Cache_Exception
*/
public function clear($mode, array $opts=array());

/**
* Find stored cached ids by matching mode and options.
*
* @param int $mode Matching mode
* @param array $opts Array of options
* @return string[] Array of stored cache ids
* @throws Zend_Cache_Exception
*/
public function find($mode, array $opts=array());

/**
* Optimize cache data structure.
*
* @param array $opts
* @throws Zend_Cache_Exception
*/
public function optimize(array $opts = array());

}
{code}
{card}

{deck}

Please take a look to my development repository to see all classes.

{zone-data}

{zone-template-instance}]]></ac:plain-text-body></ac:macro>