| <?php |
| /** |
| * File containing the ezcCacheStack class. |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| * @package Cache |
| * @version //autogentag// |
| * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 |
| * @filesource |
| */ |
| |
| /** |
| * Hierarchical caching class using multiple storages. |
| * |
| * An instance of this class can be used to achieve so called "hierarchical |
| * caching". A cache stack consists of an arbitrary number of cache storages, |
| * being sorted from top to bottom. Usually this order reflects the speed of |
| * access for the caches: The fastest cache is at the top, the slowest at the |
| * bottom. Whenever data is stored in the stack, it is stored in all contained |
| * storages. When data is to be restored, the stack will restore it from the |
| * highest storage it is found in. Data is removed from a storage, whenever the |
| * storage reached a configured number of items, using a {@link |
| * ezcCacheStackReplacementStrategy}. |
| * |
| * To create a cache stack, multiple storages are necessary. The following |
| * examples assume that $storage1, $storage2 and $storage3 contain objects that |
| * implement the {@link ezcCacheStackableStorage} interface. |
| * |
| * The slowest cache should be at the very bottom of the stack, so that items |
| * don't need to be restored from it that frequently. |
| * <code> |
| * <?php |
| * $stack = new ezcCacheStack( 'stack_id' ); |
| * $stack->pushStorage( |
| * new ezcCacheStackStorageConfiguration( |
| * 'filesystem_cache', |
| * $storage1, |
| * 1000000, |
| * .5 |
| * ) |
| * ); |
| * ?> |
| * </code> |
| * 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. |
| * <code> |
| * <?php |
| * $stack->pushStorage( |
| * new ezcCacheStackStorageConfiguration( |
| * 'apc_storage', |
| * $storage2, |
| * 10000, |
| * .8 |
| * ) |
| * ); |
| * $stack->pushStorage( |
| * new ezcCacheStackStorageConfiguration( |
| * 'custom_storage', |
| * $storage3, |
| * 50, |
| * .3 |
| * ) |
| * ); |
| * ?> |
| * </code> |
| * 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: |
| * <code> |
| * <?php |
| * $stack->options->metaStorage = $storage2; |
| * $stack->options->replacementStrategy = 'ezcCacheStackLfuReplacementStrategy'; |
| * ?> |
| * </code> |
| * $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; |
| } |
| } |
| |
| ?> |