| <?php |
| /** |
| * File containing the ezcCacheStorageMemory 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// |
| * @copyright Copyright (C) 2005-2010 eZ Systems AS. All rights reserved. |
| * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 |
| * @filesource |
| */ |
| |
| /** |
| * Base abstract class for all memory storage classes. |
| * |
| * Abstract classes extending this class: |
| * - {@link ezcCacheStorageMemcache} |
| * - {@link ezcCacheStorageApc} |
| * |
| * Implementations derived from this class and its descendants: |
| * - {@link ezcCacheStorageMemcachePlain} |
| * - {@link ezcCacheStorageApcPlain} |
| * - {@link ezcCacheStorageFileApcArray} |
| * |
| * @package Cache |
| * @version //autogentag// |
| */ |
| abstract class ezcCacheStorageMemory extends ezcCacheStorage implements ezcCacheStackableStorage, ezcCacheStackMetaDataStorage |
| { |
| /** |
| * Holds the memory backend object which communicates with the memory handler |
| * (Memcache, APC). |
| * |
| * @var ezcCacheMemoryBackend |
| */ |
| protected $backend; |
| |
| /** |
| * Holds the name of the memory backend. |
| * |
| * @var string |
| */ |
| protected $backendName; |
| |
| /** |
| * Holds the name of the registry. |
| * |
| * @var string |
| */ |
| protected $registryName; |
| |
| /** |
| * Holds the registry. |
| * |
| * @var array(mixed) |
| */ |
| protected $registry = array(); |
| |
| /** |
| * Holds the search registry. |
| * |
| * @var array(mixed) |
| */ |
| protected $searchRegistry = array(); |
| |
| /** |
| * Wether this storage holds a lock. |
| * |
| * @var bool |
| */ |
| private $lock = false; |
| |
| /** |
| * Creates a new cache storage in the given location. |
| * |
| * Options can contain the 'ttl' (Time-To-Live). Specific implementations |
| * can have additional options. |
| * |
| * @throws ezcBasePropertyNotFoundException |
| * If you tried to set a non-existent option value. The accepted |
| * options depend on the ezcCacheStorage implementation and may |
| * vary. |
| * |
| * @param string $location Path to the cache location. Null for |
| * memory-based storage and an existing |
| * writeable path for file or memory/file |
| * storage. |
| * @param array(string=>string) $options Options for the cache |
| */ |
| public function __construct( $location, array $options = array() ) |
| { |
| parent::__construct( $location, array() ); |
| } |
| |
| /** |
| * Stores data to the cache storage under the key $id. |
| * |
| * The type of cache data which is expected by an ezcCacheStorageMemory |
| * implementation depends on the backend. In most cases strings and arrays |
| * will be accepted, in some rare cases only strings might be accepted. |
| * |
| * Using attributes you can describe your cache data further. This allows |
| * you to deal with multiple cache data at once later. Some |
| * ezcCacheStorageMemory implementations also use the attributes for storage |
| * purposes. Attributes form some kind of "extended ID". |
| * |
| * @return string The ID string of the newly cached data |
| * |
| * @param string $id Unique identifier for the data |
| * @param mixed $data The data to store |
| * @param array(string=>string) $attributes Attributes describing the cached data |
| */ |
| public function store( $id, $data, $attributes = array() ) |
| { |
| // Generate the Identifier |
| $identifier = $this->generateIdentifier( $id, $attributes ); |
| $location = $this->properties['location']; |
| |
| if ( isset( $this->registry[$location][$id][$identifier] ) ) |
| { |
| unset( $this->registry[$location][$id][$identifier] ); |
| } |
| |
| // Prepare the data |
| $dataStr = $this->prepareData( $data ); |
| |
| // Store the data |
| $this->registerIdentifier( $id, $attributes, $identifier ); |
| if ( !$this->backend->store( $identifier, $dataStr, $this->properties['options']['ttl'] ) ) |
| { |
| $exceptionClass = "ezcCache{$this->backendName}Exception"; |
| throw new $exceptionClass( "{$this->backendName} store failed." ); |
| } |
| |
| return $id; |
| } |
| |
| /** |
| * Restores the data from the cache. |
| * |
| * During access to cached data the caches are automatically |
| * expired. This means, that the ezcCacheStorageMemory object checks |
| * before returning the data if it's still actual. If the cache |
| * has expired, data will be deleted and false is returned. |
| * |
| * You should always provide the attributes you assigned, although |
| * the cache storages must be able to find a cache ID even without |
| * them. BEWARE: Finding cache data only by ID can be much |
| * slower than finding it by ID and attributes. |
| * |
| * @param string $id The item ID to restore |
| * @param array(string=>string) $attributes Attributes describing the data to restore |
| * @param bool $search Whether to search for items if not found directly |
| * @return mixed The cached data on success, otherwise false |
| */ |
| public function restore( $id, $attributes = array(), $search = false ) |
| { |
| // Generate the Identifier |
| $identifier = $this->generateIdentifier( $id, $attributes ); |
| $location = $this->properties['location']; |
| |
| // Creates a registry object |
| if ( !isset( $this->registry[$location][$id][$identifier] ) ) |
| { |
| if ( !isset( $this->registry[$location] ) ) |
| { |
| $this->registry[$location] = array(); |
| } |
| if ( !isset( $this->registry[$location][$id] ) ) |
| { |
| $this->registry[$location][$id] = array(); |
| } |
| $this->registry[$location][$id][$identifier] = $this->fetchData( $identifier, true ); |
| } |
| |
| // Makes sure a cache exists |
| if ( $this->registry[$location][$id][$identifier] === false ) |
| { |
| if ( $search === true |
| && count( $identifiers = $this->search( $id, $attributes ) ) === 1 ) |
| { |
| $identifier = $identifiers[0][2]; |
| $this->registry[$location][$id][$identifier] = $this->fetchData( $identifier, true ); |
| } |
| else |
| { |
| // There are more elements found during search, so false is returned |
| return false; |
| } |
| } |
| |
| // Make sure the data is not supposed to be expired |
| if ( $this->properties['options']['ttl'] !== false |
| && $this->calcLifetime( $identifier, $this->registry[$location][$id][$identifier] ) == 0 ) |
| { |
| $this->delete( $id, $attributes, false ); |
| return false; |
| } |
| |
| // Return the data |
| $data = is_object( $this->registry[$location][$id][$identifier] ) ? $this->registry[$location][$id][$identifier]->data : false; |
| if ( $data !== false ) |
| { |
| return $data; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| /** |
| * Deletes the data associated with $id or $attributes from the cache. |
| * |
| * Additional attributes provided will matched additionally. This can give |
| * you an immense speed improvement against just searching for ID (see |
| * {@link ezcCacheStorage::restore()}). |
| * |
| * If you only provide attributes for deletion of cache data, all cache |
| * data matching these attributes will be purged. |
| * |
| * @throws ezcBaseFilePermissionException |
| * If an already existsing cache file could not be unlinked. |
| * This exception means most likely that your cache directory |
| * has been corrupted by external influences (file permission |
| * change). |
| * |
| * @param string $id The item ID to purge |
| * @param array(string=>string) $attributes Attributes describing the data to restore |
| * @param bool $search Whether to search for items if not found directly |
| */ |
| public function delete( $id = null, $attributes = array(), $search = false ) |
| { |
| // Generate the Identifier |
| $identifier = $this->generateIdentifier( $id, $attributes ); |
| $location = $this->properties['location']; |
| |
| // Finds the caches that require deletion |
| $delCaches = array(); |
| if ( $this->fetchData( $identifier ) !== false ) |
| { |
| $delCaches[] = array( $id, $attributes, $identifier ); |
| } |
| else if ( $search === true ) |
| { |
| $delCaches = $this->search( $id, $attributes ); |
| } |
| |
| $deletedIds = array(); |
| |
| // Process the caches to delete |
| $identifiers = array(); |
| foreach ( $delCaches as $cache ) |
| { |
| $this->backend->delete( $cache[2] ); |
| $deletedIds[] = $cache[0]; |
| $this->unRegisterIdentifier( $cache[0], $cache[1], $cache[2], true ); |
| if ( isset( $this->registry[$location][$cache[0]][$cache[2]] ) ) |
| { |
| unset( $this->registry[$location][$cache[0]][$cache[2]] ); |
| } |
| } |
| $this->storeSearchRegistry(); |
| |
| return $deletedIds; |
| } |
| |
| /** |
| * Returns the number of items in the cache matching a certain criteria. |
| * |
| * This method determines if cache data described by the given ID and/or |
| * attributes exists. It returns the number of cache data items found. |
| * |
| * @param string $id The item ID |
| * @param array(string=>string) $attributes Attributes describing the data |
| * @return int Number of data items matching the criteria |
| */ |
| public function countDataItems( $id = null, $attributes = array() ) |
| { |
| return count( $this->search( $id, $attributes ) ); |
| } |
| |
| /** |
| * Returns the time in seconds which remains for a cache object, before it |
| * gets outdated. In case the cache object is already outdated or does not |
| * exists, this method returns 0. |
| * |
| * @param string $id The item ID |
| * @param array(string=>string) $attributes Attributes describing the data |
| * @return int The remaining lifetime (0 if it does not exist or outdated) |
| */ |
| public function getRemainingLifetime( $id, $attributes = array() ) |
| { |
| if ( count( $found = $this->search( $id, $attributes ) ) > 0 ) |
| { |
| $identifier = $found[0][2]; |
| return $this->calcLifetime( $identifier ); |
| } |
| return 0; |
| } |
| |
| /** |
| * Generates the storage internal identifier from ID and attributes. |
| * |
| * @param string $id The ID |
| * @param array(string=>string) $attributes Attributes describing the data |
| * @return string The generated identifier |
| */ |
| public function generateIdentifier( $id, $attributes = null ) |
| { |
| $identifier = strtolower( $this->backendName ) |
| . $this->properties['location'] |
| . $id |
| . ( |
| ( $attributes !== null && !empty( $attributes ) ) |
| ? md5( serialize( $attributes ) ) |
| : '' |
| ); |
| return urlencode( $identifier ); |
| } |
| |
| /** |
| * Purge outdated data from the storage. |
| * |
| * This method purges outdated data from the cache. If $limit is given, a |
| * maximum of $limit items is purged. Otherwise all outdated items are |
| * purged. The method returns an array containing the IDs of all cache |
| * items that have been purged. |
| * |
| * @param int $limit |
| * @return array(string) |
| */ |
| public function purge( $limit = null ) |
| { |
| $this->fetchSearchRegistry( true ); |
| |
| $purgedIds = array(); |
| $ttl = $this->properties['options']->ttl; |
| |
| foreach ( $this->searchRegistry[$this->properties['location']] as $id => $identifiers ) |
| { |
| $deleted = false; |
| foreach( $identifiers as $identifier => $data ) |
| { |
| if ( $ttl !== false && $this->calcLifetime( $identifier ) == 0 ) |
| { |
| // Since ID <-> identifier mapping is ambigious, this does |
| // not ensure that all data for an ID is deleted. However, |
| // this should work if used properly |
| $this->backend->delete( $identifier ); |
| $this->unRegisterIdentifier( null, null, $identifiers, true ); |
| // Avoid adding an ID twice to the returned array |
| $deleted = true; |
| } |
| } |
| if ( $deleted === true ) |
| { |
| $purgedIds[] = $id; |
| } |
| if ( $limit !== null && count( $purgedIds ) >= $limit ) |
| { |
| break; |
| } |
| } |
| $this->storeSearchRegistry(); |
| return $purgedIds; |
| } |
| |
| /** |
| * Reset the complete storage. |
| * |
| * This method resets the complete cache storage. All content (including |
| * content stored with the {@link ezcCacheStackMetaDataStorage} interfacer) must |
| * be deleted and the cache storage must appear as if it has just newly |
| * been created. |
| * |
| * @return void |
| */ |
| public function reset() |
| { |
| $this->backend->reset(); |
| $this->registry = array(); |
| $this->searchRegistry = array( $this->properties['location'] => null ); |
| $this->storeSearchRegistry(); |
| } |
| |
| /** |
| * Restores and returns the meta data struct. |
| * |
| * This method fetches the meta data stored in the storage and returns the |
| * according struct of type {@link ezcCacheStackMetaData}. The meta data |
| * must be stored inside the storage, but should not be visible as normal |
| * cache items to the user. |
| * |
| * @return ezcCacheStackMetaData |
| */ |
| public function restoreMetaData() |
| { |
| $metaDataKey = urlencode( $this->properties['location'] ) . '_' |
| . $this->properties['options']->metaDataKey; |
| |
| if ( ( $data = $this->backend->fetch( $metaDataKey ) ) === false ) |
| { |
| $data = null; |
| } |
| return $data; |
| } |
| |
| /** |
| * Stores the given meta data struct. |
| * |
| * This method stores the given $metaData inside the storage. The data must |
| * be stored with the same mechanism that the storage itself uses. However, |
| * it should not be stored as a normal cache item, if possible, to avoid |
| * accedental user manipulation. |
| * |
| * @param ezcCacheStackMetaData $metaData |
| * @return void |
| */ |
| public function storeMetaData( ezcCacheStackMetaData $metaData ) |
| { |
| $metaDataKey = urlencode( $this->properties['location'] ) . '_' |
| . $this->properties['options']->metaDataKey; |
| |
| $this->backend->store( |
| $metaDataKey, |
| $metaData |
| ); |
| } |
| |
| /** |
| * Acquire a lock on the storage. |
| * |
| * This method acquires a lock on the storage. If locked, the storage must |
| * block all other method calls until the lock is freed again using {@link |
| * ezcCacheStackMetaDataStorage::unlock()}. Methods that are called within |
| * the request that successfully acquired the lock must succeed as usual. |
| * |
| * @return void |
| */ |
| public function lock() |
| { |
| $lockKey = urlencode( $this->properties['location'] ) . '_' |
| . $this->properties['options']->lockKey; |
| $this->backend->acquireLock( |
| $lockKey, |
| $this->properties['options']->lockWaitTime, |
| $this->properties['options']->maxLockTime |
| ); |
| $this->lock = true; |
| } |
| |
| /** |
| * Release a lock on the storage. |
| * |
| * This method releases the lock of the storage, that has been acquired via |
| * {@link ezcCacheStackMetaDataStorage::lock()}. After this method has been |
| * called, blocked method calls (including calls to lock()) can suceed |
| * again. |
| * |
| * @return void |
| */ |
| public function unlock() |
| { |
| if ( $this->lock !== false ) |
| { |
| $lockKey = urlencode( $this->properties['location'] ) . '_' |
| . $this->properties['options']->lockKey; |
| $this->backend->releaseLock( |
| $lockKey |
| ); |
| $this->lock = false; |
| } |
| } |
| |
| /** |
| * Calculates the lifetime remaining for a cache object. |
| * |
| * In case the TTL options is set to true, this method always returns 1. |
| * |
| * @param string $identifier The memcache identifier |
| * @param bool $dataObject The optional data object for which to calculate the lifetime |
| * @return int The remaining lifetime in seconds (0 if no time remaining) |
| */ |
| protected function calcLifetime( $identifier, $dataObject = false ) |
| { |
| $ttl = $this->options->ttl; |
| $dataObject = is_object( $dataObject ) ? $dataObject : $this->fetchData ( $identifier, true ); |
| if ( is_object( $dataObject ) ) |
| { |
| if ( $ttl === false ) |
| { |
| return 1; |
| } |
| return ( |
| ( $lifeTime = ( time() - $dataObject->time ) < $ttl ) |
| ? $ttl - $lifeTime |
| : 0 |
| ); |
| } |
| else |
| { |
| return 0; |
| } |
| } |
| |
| /** |
| * Registers an identifier to facilitate searching. |
| * |
| * @param string $id ID for the cache item |
| * @param array $attributes Attributes for the cache item |
| * @param string $identifier Identifier generated for the cache item |
| */ |
| protected function registerIdentifier( $id = null, $attributes = array(), $identifier = null ) |
| { |
| $identifier = ( $identifier !== null ) ? $identifier : $this->generateIdentifier( $id, $attributes ); |
| $location = $this->properties['location']; |
| |
| $this->fetchSearchRegistry(); |
| |
| // Makes sure the identifier exists |
| if ( !isset( $this->searchRegistry[$location][$id][$identifier] ) ) |
| { |
| // Makes sure the id exists |
| if ( !isset( $this->searchRegistry[$location][$id] ) |
| || !is_array( $this->searchRegistry[$location][$id] ) ) |
| { |
| $this->searchRegistry[$location][$id] = array(); |
| } |
| |
| $this->searchRegistry[$location][$id][$identifier] = new ezcCacheStorageMemoryRegisterStruct( $id, $attributes, $identifier, $location ); |
| $this->storeSearchRegistry(); |
| } |
| } |
| |
| /** |
| * Un-registers a previously registered identifier. |
| * |
| * @param string $id ID for the cache item |
| * @param array $attributes Attributes for the cache item |
| * @param string $identifier Identifier generated for the cache item |
| * @param bool $delayStore Delays the storing of the updated search registry |
| */ |
| protected function unRegisterIdentifier( $id = null, $attributes = array(), $identifier = null, $delayStore = false ) |
| { |
| $identifier = ( $identifier !== null ) ? $identifier : $this->generateIdentifier( $id, $attributes ); |
| $location = $this->properties['location']; |
| |
| $this->fetchSearchRegistry( !$delayStore ); |
| |
| if ( $this->searchRegistry === false ) |
| { |
| $this->searchRegistry = array(); |
| } |
| |
| if ( isset( $this->searchRegistry[$location][$id][$identifier] ) ) |
| { |
| unset( $this->searchRegistry[$location][$id][$identifier], $this->registry[$location][$id][$identifier] ); |
| if ( $delayStore === false ) |
| { |
| $this->storeSearchRegistry(); |
| } |
| } |
| } |
| |
| /** |
| * Fetches the search registry from the backend or creates it if empty. |
| * |
| * @param bool $requireFresh To create a new search registry or not |
| */ |
| protected function fetchSearchRegistry( $requireFresh = false ) |
| { |
| $location = $this->properties['location']; |
| if ( !is_array( $this->searchRegistry ) ) |
| { |
| $this->searchRegistry = array(); |
| } |
| if ( !isset( $this->searchRegistry[$location] ) |
| || !is_array( $this->searchRegistry[$location] ) ) |
| { |
| $this->searchRegistry[$location] = array(); |
| } |
| |
| // Makes sure the registry exists |
| if ( empty( $this->searchRegistry[$location] ) |
| || $requireFresh === true ) |
| { |
| $this->searchRegistry[$location] = $this->backend->fetch( $this->registryName . '_' . urlencode( $location ) ); |
| } |
| } |
| |
| /** |
| * Stores the search registry in the backend. |
| */ |
| protected function storeSearchRegistry() |
| { |
| $location = $this->properties['location']; |
| |
| $this->backend->store( $this->registryName . '_' . urlencode( $location ), $this->searchRegistry[$location] ); |
| |
| $this->searchRegistry[$location] = null; |
| $this->fetchSearchRegistry( true ); |
| } |
| |
| /** |
| * Generates a string from the $attributes array. |
| * |
| * @param array(string=>string) $attributes Attributes describing the data |
| * @return string |
| * |
| * @apichange Was only used to generate "pseudo-regex". Attribute arrays |
| * are compared directly now. |
| */ |
| protected function generateAttrStr( $attributes = array() ) |
| { |
| ksort( $attributes ); |
| $attrStr = ''; |
| foreach ( $attributes as $key => $val ) |
| { |
| $attrStr .= '-' . $key . '=' .$val; |
| } |
| return $attrStr; |
| } |
| |
| /** |
| * Checks if the location property is valid. |
| */ |
| protected function validateLocation() |
| { |
| return; |
| } |
| |
| /** |
| * Searches the storage for data defined by ID and/or attributes. |
| * |
| * @param string $id The item ID |
| * @param array(string=>string) $attributes Attributes describing the data |
| * @return array(mixed) |
| */ |
| protected function search( $id = null, $attributes = array() ) |
| { |
| // Grabs the identifier registry |
| $this->fetchSearchRegistry(); |
| |
| // Grabs the $location |
| $location = $this->properties['location']; |
| |
| // Finds all in case of empty $id and $attributes |
| if ( $id === null |
| && empty( $attributes ) |
| && isset( $this->searchRegistry[$location] ) |
| && is_array( $this->searchRegistry[$location] ) ) |
| { |
| $itemArr = array(); |
| foreach ( $this->searchRegistry[$location] as $idArr ) |
| { |
| foreach ( $idArr as $registryObj ) |
| { |
| if ( !is_null( $registryObj->id ) ) |
| { |
| $itemArr[] = array( $registryObj->id, $registryObj->attributes, $registryObj->identifier ); |
| } |
| } |
| } |
| return $itemArr; |
| } |
| |
| $itemArr = array(); |
| // Makes sure we've seen this ID before |
| if ( isset( $this->searchRegistry[$location][$id] ) |
| && is_array( $this->searchRegistry[$location][$id] ) ) |
| { |
| foreach ( $this->searchRegistry[$location][$id] as $identifier => $dataArr ) |
| { |
| if ( $this->fetchData( $identifier ) !== false ) |
| { |
| $itemArr[] = array( $id, $attributes, $identifier ); |
| } |
| } |
| } |
| else |
| { |
| // Finds cache items that fit our description |
| if ( isset( $this->searchRegistry[$location] ) |
| && is_array( $this->searchRegistry[$location] ) ) |
| { |
| foreach ( $this->searchRegistry[$location] as $id => $arr ) |
| { |
| foreach ( $arr as $identifier => $registryObj ) |
| { |
| if ( count( array_diff_assoc( $attributes, $registryObj->attributes ) ) === 0 ) |
| { |
| $itemArr[] = array( |
| $registryObj->id, |
| $registryObj->attributes, |
| $registryObj->identifier |
| ); |
| } |
| } |
| } |
| } |
| } |
| return $itemArr; |
| } |
| } |
| ?> |