* pushStorage( * new ezcCacheStackStorageConfiguration( * 'filesystem_cache', * $storage1, * 1000000, * .5 * ) * ); * ?> * * This operations create a new cache stack and add $storage1 to its very * bottom. The first parameter for {@linke ezcCacheStackStorageConfiguration} * are a unique ID for the storage inside the stack, which should never change. * The second parameter is the storage object itself. Parameter 3 is the * maximum number of items the storage might contain. As soon as this limit ist * reached, 500000 items will be purged from the cache. The latter number is * defined through the fourth parameter, indicating the fraction of the stored * item number, that is to be freed. For freeing, first already outdated items * will be purged. If this does not last, more items will be freed using the * {@link ezcCacheStackReplacementStrategy} used by the stack. * * The following code adds the other 2 storages to the stack, where $storage2 * is a memory storage {@link ezcCacheStackMemoryStorage} and $storage3 is a * custom storage implementation, that stores objects in the current requests * memory. * * pushStorage( * new ezcCacheStackStorageConfiguration( * 'apc_storage', * $storage2, * 10000, * .8 * ) * ); * $stack->pushStorage( * new ezcCacheStackStorageConfiguration( * 'custom_storage', * $storage3, * 50, * .3 * ) * ); * ?> * * The second level of the cache build by $storage2. This storage will contain * 10000 items maximum and when it runs full, 8000 items will be deleted from * it. The top level of the cache is the custom cache storage, that will only * contain 50 objects in the currently processed request. If this storage ever * runs full, which should not happen within a request, 15 items will be * removed from it. * * Since the top most storage in this example does not implement {@link * ezcCacheStackMetaDataStorage}, another storage must be defined to be used * for storing meta data about the stack: * * options->metaStorage = $storage2; * $stack->options->replacementStrategy = 'ezcCacheStackLfuReplacementStrategy'; * ?> * * $storage2 is defined to store the meta information for the stack. In * addition, a different replacement strategy than the default {@link * ezcCacheStackLruReplacementStrategy} replacement strategy is defined. LFU * removes least frequently used items, while LRU deletes least recently used * items from a storage, if it runs full. * * Using the $bubbleUpOnRestore option you can determine, that data which is * restored from a lower storage will be automatically stored in all higher * storages again. The problem with this is, that only the attributes that are * used for restoring will be assigned to the item in higher storages. In * addition, the items will be stored with a fresh TTL. Therefore, this * behavior is not recommended. * * If you want to use a cache stack in combination with {@ezcCacheManager}, you * will most likely want to use {@link ezcCacheStackConfigurator}. This allows * you to let the stack be configured on the fly, when it is used for the first * time in a request. * * Beware, that whenever you change the structure of your stack or the * replacement strategy between 2 requests, you should always perform a * complete {@link ezcCacheStack::reset()} on it. Otherwise, your cache data * might be seriously broken which will result in undefined behaviour of the * stack. Changing the replacement strategy will most possibly result in an * {@link ezcCacheInvalidMetaDataException}. * * @property ezcCacheStackOptions $options * Options for the cache stack. * @property string $location * Location of this stack. Unused. * @mainclass * @package Cache * @version //autogentag// */ class ezcCacheStack extends ezcCacheStorage { /** * Stack of storages. * * @var array(int=>ezcCacheStackableStorage) */ private $storageStack = array(); /** * Mapping if IDs to storages. * * Mainly used to validate an ID is not taken twice and a storage is not * added twice. * * @var array(string=>ezcCacheStackableStorage) */ private $storageIdMap = array(); /** * Creates a new cache stack. * * Usually you will want to use the {@link ezcCacheManager} to take care of * your caches. The $options ({@link ezcCacheStackOptions}) stored in the * manager and given here can contain a class reference to an * implementation of {@link ezcCacheStackConfigurator} that will be used to * initially configure the stack instance directly after construction. * * To perform manual configuration of the stack the {@link * ezcCacheStack::pushStorage()} and {@link ezcCacheStack::popStorage()} * methods can be used. * * The location can be a free form string that identifies the stack * uniquely in the {@link ezcCacheManager}. It is currently not used * internally in the stack. * * @param string $location * @param ezcCacheStackOptions $options */ public function __construct( $location, ezcCacheStackOptions $options = null ) { if ( $options === null ) { $options = new ezcCacheStackOptions(); } $this->properties['location'] = $location; $this->properties['options'] = $options; if ( $options->configurator !== null ) { // Configure this instance call_user_func( array( $options->configurator, 'configure' ), $this ); } } /** * Stores data in the cache stack. * * This method will store the given data across the complete stack. It can * afterwards be restored using the {@link ezcCacheStack::restore()} method * and be deleted through the {@link ezcCacheStack::delete()} method. * * The data that can be stored in the cache depends on the configured * storages. Usually it is save to store arrays and scalars. However, some * caches support objects or do not even support arrays. You need to make * sure that the inner caches of the stack *all* support the data you want * to store! * * The $attributes array is optional and can be used to describe the stack * further. * * @param string $id * @param mixed $data * @param array $attributes */ public function store( $id, $data, $attributes = array() ) { $metaStorage = $this->getMetaDataStorage(); $metaStorage->lock(); $metaData = $this->getMetaData( $metaStorage ); foreach( $this->storageStack as $storageConf ) { call_user_func( array( $this->properties['options']->replacementStrategy, 'store' ), $storageConf, $metaData, $id, $data, $attributes ); } $metaStorage->storeMetaData( $metaData ); $metaStorage->unlock(); } /** * Returns the meta data to use. * * Returns the meta data to use with the configured {@link * ezcCacheStackStackReplacementStrategy}. * * @param ezcCacheStackMetaDataStorage $metaStorage * @return ezcCacheMetaData */ private function getMetaData( ezcCacheStackMetaDataStorage $metaStorage ) { $metaData = $metaStorage->restoreMetaData(); if ( $metaData === null ) { $metaData = call_user_func( array( $this->properties['options']->replacementStrategy, 'createMetaData' ) ); } return $metaData; } /** * Restores an item from the stack. * * This method tries to restore an item from the cache stack and returns * the found data, if any. If no data is found, boolean false is returned. * Given the ID of an object will restore exactly the desired object. If * additional $attributes are given, this may speed up the restore process * for some caches. If only attributes are given, the $search parameter * makes sense, since it will instruct the inner cach storages to search * their content for the given attribute combination and will restore the * first item found. However, this process is much slower then restoring * with an ID and therefore is not recommended. * * @param string $id * @param array $attributes * @param bool $search * @return mixed The restored data or false on failure. */ public function restore( $id, $attributes = array(), $search = false ) { $metaStorage = $this->getMetaDataStorage(); $metaStorage->lock(); $metaData = $this->getMetaData( $metaStorage ); $item = false; foreach ( $this->storageStack as $storageConf ) { $item = call_user_func( array( $this->properties['options']->replacementStrategy, 'restore' ), $storageConf, $metaData, $id, $attributes, $search ); if ( $item !== false ) { if ( $this->properties['options']->bubbleUpOnRestore ) { $this->bubbleUp( $id, $attributes, $item, $storageConf, $metaData ); } // Found, so end. break; } } $metaStorage->storeMetaData( $metaData ); $metaStorage->unlock(); return $item; } /** * Bubbles a restored $item up to all storages above $foundStorageConf. * * @param string $id * @param array $attributes * @param mixed $item * @param ezcCacheStackStorageConfiguration $foundStorageConf * @param ezcCacheStackMetaData $metaData */ private function bubbleUp( $id, array $attributes, $item, ezcCacheStackStorageConfiguration $foundStorageConf, ezcCacheStackMetaData $metaData ) { foreach( $this->storageStack as $storageConf ) { if ( $storageConf === $foundStorageConf ) { // This was the storage where we restored break; } call_user_func( array( $this->properties['options']->replacementStrategy, 'store' ), $storageConf, $metaData, $id, $item, $attributes ); } } /** * Deletes an item from the stack. * * This method deletes an item from the cache stack. The item will * afterwards no more be stored in any of the inner cache storages. Giving * the ID of the cache item will delete exactly 1 desired item. Giving an * attribute array, describing the desired item in more detail, can speed * up the deletion in some caches. * * Giving null as the ID and just an attribibute array, with the $search * attribute in addition, will delete *all* items that comply to the * attributes from all storages. This might be much slower than just * deleting a single item. * * Deleting items from a cache stack is not recommended at all. Instead, * items should expire on their own or be overwritten with more actual * data. * * The method returns an array containing all deleted item IDs. * * @param string $id * @param array $attributes * @param bool $search * @return array(string) */ public function delete( $id = null, $attributes = array(), $search = false ) { $metaStorage = $this->getMetaDataStorage(); $metaStorage->lock(); $metaData = $metaStorage->restoreMetaData(); $deletedIds = array(); foreach ( $this->storageStack as $storageConf ) { $deletedIds = array_merge( $deletedIds, call_user_func( array( $this->properties['options']->replacementStrategy, 'delete' ), $storageConf, $metaData, $id, $attributes, $search ) ); } $metaStorage->storeMetaData( $metaData ); $metaStorage->unlock(); return array_unique( $deletedIds ); } /** * Returns the meta data storage to be used. * * Determines the meta data storage to be used by the stack and returns it. * * @return ezcCacheStackMetaData */ private function getMetaDataStorage() { $metaStorage = $this->options->metaStorage; if ( $metaStorage === null ) { $metaStorage = reset( $this->storageStack )->storage; if ( !( $metaStorage instanceof ezcCacheStackMetaDataStorage ) ) { throw new ezcBaseValueException( 'metaStorage', $metaStorage, 'ezcCacheStackMetaDataStorage', 'top of storage stack' ); } } return $metaStorage; } /** * Counts how many items are stored, fulfilling certain criteria. * * This method counts how many data items fulfilling the given criteria are * stored overall. Note: The items of all contained storages are counted * independantly and summarized. * * @param string $id * @param array $attributes * @return int */ public function countDataItems( $id = null, $attributes = array() ) { $sum = 0; foreach( $this->storageStack as $storageConf ) { $sum += $storageConf->storage->countDataItems( $id, $attributes ); } return $sum; } /** * Returns the remaining lifetime for the given item ID. * * This method returns the lifetime in seconds for the item identified by $item * and optionally described by $attributes. Definining the $attributes * might lead to faster results with some caches. * * The first internal storage that is found for the data item is chosen to * detemine the lifetime. If no storage contains the item or the item is * outdated in all found caches, 0 is returned. * * @param string $id * @param array $attributes * @return int */ public function getRemainingLifetime( $id, $attributes = array() ) { foreach ( $this->storageStack as $storageConf ) { $lifetime = $storageConf->storage->getRemainingLifetime( $id, $attributes ); if ( $lifetime > 0 ) { return $lifetime; } } return 0; } /** * Add a storage to the top of the stack. * * This method is used to add a new storage to the top of the cache. The * $storageConf of type {@link ezcCacheStackStorageConfiguration} consists * of the actual {@link ezcCacheStackableStorage} and other information. * * Most importantly, the configuration object contains an ID, which must be * unique within the whole storage. The itemLimit setting determines how * many items might be stored in the storage at all. freeRate determines * which fraction of itemLimit will be freed by the {@link * ezcCacheStackStackReplacementStrategy} of the stack, when the storage * runs full. * * @param ezcCacheStackStorageConfiguration $storageConf */ public function pushStorage( ezcCacheStackStorageConfiguration $storageConf ) { if ( isset( $this->storageIdMap[$storageConf->id] ) ) { throw new ezcCacheStackIdAlreadyUsedException( $storageConf->id ); } if ( in_array( $storageConf->storage, $this->storageIdMap, true ) ) { throw new ezcCacheStackStorageUsedTwiceException( $storageConf->storage ); } array_unshift( $this->storageStack, $storageConf ); $this->storageIdMap[$storageConf->id] = $storageConf->storage; } /** * Removes a storage from the top of the stack. * * This method can be used to remove the top most {@link * ezcCacheStackableStorage} from the stack. This is commonly done to * remove caches or to insert new ones into lower positions. In both cases, * it is recommended to {@link ezcCacheStack::reset()} the whole cache * afterwards to avoid any kind of inconsistency. * * @return ezcCacheStackStorageConfiguration * * @throws ezcCacheStackUnderflowException * if called on an empty stack. */ public function popStorage() { if ( count( $this->storageStack ) === 0 ) { throw new ezcCacheStackUnderflowException(); } $storageConf = array_shift( $this->storageStack ); unset( $this->storageIdMap[$storageConf->id] ); return $storageConf; } /** * Returns the number of storages on the stack. * * Returns the number of storages currently on the stack. * * @return int */ public function countStorages() { return count( $this->storageStack ); } /** * Returns all stacked storages. * * This method returns the whole stack of {@link ezcCacheStackableStorage} * as an array. This maybe useful to adjust options of the storages after * they have been added to the stack. However, it is not recommended to * perform any drastical changes to the configurations. Performing manual * stores, restores and deletes on the storages is *highly discouraged*, * since it may lead to irrepairable inconsistencies of the stack. * * @return array(ezcCacheStackableStorage) */ public function getStorages() { return $this->storageStack; } /** * Resets the complete stack. * * This method is used to reset the complete stack of storages. It will * reset all storages, using {@link ezcCacheStackableStorage::reset()}, and * therefore purge the complete content of the stack. In addition, it will * kill the complete meta data. * * The stack is in a consistent, but empty, state afterwards. * * @return void */ public function reset() { foreach ( $this->storageStack as $storageConf ) { $storageConf->storage->reset(); } } /** * Validates the $location parameter of the constructor. * * Returns true, since $location is not necessary for this storage. * * @return bool */ protected function validateLocation() { // Does not utilize $location return true; } /** * Sets the options for this stack instance. * * Overwrites the parent implementation to only allow instances of {@link * ezcCacheStackOptions}. * * @param ezcCacheStackOptions $options * * @apichange Use $stack->options instead. */ public function setOptions( $options ) { // Overloading $this->options = $options; } /** * Property write access. * * @param string $propertyName Name of the property. * @param mixed $propertyValue The value for the property. * * @throws ezcBaseValueException * If the value for the property options is not an instance of * ezcCacheStackOptions. * @ignore */ public function __set( $propertyName, $propertyValue ) { switch ( $propertyName ) { case 'options': if ( !( $propertyValue instanceof ezcCacheStackOptions ) ) { throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcCacheStackOptions' ); } break; default: parent::__set( $propertyName, $propertyValue ); return; } $this->properties[$propertyName] = $propertyValue; } } ?>