2 /***************************************************************
5 * (c) 2009 Ingo Renner <ingo@typo3.org>
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
17 * This script is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * This copyright notice MUST APPEAR in all copies of the script!
23 ***************************************************************/
27 * A caching backend which stores cache entries by using APC.
29 * This backend uses the following types of keys:
31 * xxx is tag name, value is array of associated identifiers identifier. This
32 * is "forward" tag index. It is mainly used for obtaining content by tag
33 * (get identifier by tag -> get content by identifier)
35 * xxx is identifier, value is array of associated tags. This is "reverse" tag
36 * index. It provides quick access for all tags associated with this identifier
37 * and used when removing the identifier
39 * Value is a List of all tags (array)
41 * Each key is prepended with a prefix. By default prefix consists from two parts
42 * separated by underscore character and ends in yet another underscore character:
44 * - MD5 of script path and filename and SAPI name
45 * This prefix makes sure that keys from the different installations do not
48 * This file is a backport from FLOW3
51 * @subpackage t3lib_cache
54 class t3lib_cache_backend_ApcBackend
extends t3lib_cache_backend_AbstractBackend
{
57 * A prefix to seperate stored data from other data possible stored in the APC
61 protected $identifierPrefix;
64 * Constructs this backend
66 * @param mixed $options Configuration options - unused here
67 * @author Robert Lemke <robert@typo3.org>
68 * @author Karsten Dambekalns <karsten@typo3.org>
70 public function __construct($options = array()) {
71 if (!extension_loaded('apc')) {
72 throw new t3lib_cache_Exception(
73 'The PHP extension "apc" must be installed and loaded in order to use the APC backend.',
78 parent
::__construct($options);
82 * Saves data in the cache.
84 * Note on lifetime: the number of seconds may not exceed 2592000 (30 days),
85 * otherwise it is interpreted as a UNIX timestamp (seconds since epoch).
87 * @param string $entryIdentifier An identifier for this specific cache entry
88 * @param string $data The data to be stored
89 * @param array $tags Tags to associate with this cache entry
90 * @param integer $lifetime Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited liftime.
92 * @throws t3lib_cache_Exception if no cache frontend has been set.
93 * @throws InvalidArgumentException if the identifier is not valid
94 * @throws t3lib_cache_exception_InvalidData if $data is not a string
95 * @author Christian Jul Jensen <julle@typo3.org>
96 * @author Karsten Dambekalns <karsten@typo3.org>
98 public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
99 if (!$this->cache
instanceof t3lib_cache_frontend_Frontend
) {
100 throw new t3lib_cache_Exception(
101 'No cache frontend has been set yet via setCache().',
106 if (!is_string($data)) {
107 throw new t3lib_cache_exception_InvalidData(
108 'The specified data is of type "' . gettype($data) . '" but a string is expected.',
113 $tags[] = '%APCBE%' . $this->cache
->getIdentifier();
114 $expiration = $lifetime !== NULL ?
$lifetime : $this->defaultLifetime
;
116 $success = apc_store($this->identifierPrefix
. $entryIdentifier, $data, $expiration);
117 if ($success === TRUE) {
118 $this->removeIdentifierFromAllTags($entryIdentifier);
119 $this->addTagsToTagIndex($tags);
120 $this->addIdentifierToTags($entryIdentifier, $tags);
122 throw new t3lib_cache_Exception(
123 'Could not set value.',
130 * Loads data from the cache.
132 * @param string $entryIdentifier An identifier which describes the cache entry to load
133 * @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
134 * @author Karsten Dambekalns <karsten@typo3.org>
136 public function get($entryIdentifier) {
138 $value = apc_fetch($this->identifierPrefix
. $entryIdentifier, $success);
140 return ($success ?
$value : $success);
144 * Checks if a cache entry with the specified identifier exists.
146 * @param string $entryIdentifier An identifier specifying the cache entry
147 * @return boolean TRUE if such an entry exists, FALSE if not
148 * @author Karsten Dambekalns <karsten@typo3.org>
150 public function has($entryIdentifier) {
152 apc_fetch($this->identifierPrefix
. $entryIdentifier, $success);
157 * Removes all cache entries matching the specified identifier.
158 * Usually this only affects one entry but if - for what reason ever -
159 * old entries for the identifier still exist, they are removed as well.
161 * @param string $entryIdentifier Specifies the cache entry to remove
162 * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
163 * @author Christian Jul Jensen <julle@typo3.org>
164 * @author Karsten Dambekalns <karsten@typo3.org>
166 public function remove($entryIdentifier) {
167 $this->removeIdentifierFromAllTags($entryIdentifier);
169 return apc_delete($this->identifierPrefix
. $entryIdentifier);
173 * Finds and returns all cache entry identifiers which are tagged by the
176 * @param string $tag The tag to search for
177 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
178 * @author Karsten Dambekalns <karsten@typo3.org>
180 public function findIdentifiersByTag($tag) {
182 $identifiers = apc_fetch($this->identifierPrefix
. 'tag_' . $tag, $success);
184 if ($success === FALSE) {
187 return (array) $identifiers;
192 * Finds and returns all cache entry identifiers which are tagged by the
195 * @param array Array of tags to search for
196 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
197 * @author Ingo Renner <ingo@typo3.org>
199 public function findIdentifiersByTags(array $tags) {
200 $taggedEntries = array();
201 $foundEntries = array();
203 foreach ($tags as $tag) {
204 $taggedEntries[$tag] = $this->findIdentifiersByTag($tag);
207 $intersectedTaggedEntries = call_user_func_array('array_intersect', $taggedEntries);
209 foreach ($intersectedTaggedEntries as $entryIdentifier) {
210 if ($this->has($entryIdentifier)) {
211 $foundEntries[$entryIdentifier] = $entryIdentifier;
215 return $foundEntries;
219 * Finds all tags for the given identifier. This function uses reverse tag
220 * index to search for tags.
222 * @param string $identifier Identifier to find tags by
223 * @return array Array with tags
224 * @author Karsten Dambekalns <karsten@typo3.org>
226 protected function findTagsByIdentifier($identifier) {
228 $tags = apc_fetch($this->identifierPrefix
. 'ident_' . $identifier, $success);
230 return ($success ?
(array)$tags : array());
234 * Removes all cache entries of this cache.
237 * @author Karsten Dambekalns <karsten@typo3.org>
239 public function flush() {
240 if (!$this->cache
instanceof t3lib_cache_frontend_Frontend
) {
241 throw new t3lib_cache_Exception(
242 'Yet no cache frontend has been set via setCache().',
247 $this->flushByTag('%APCBE%' . $this->cache
->getIdentifier());
251 * Removes all cache entries of this cache which are tagged by the specified
254 * @param string $tag The tag the entries must have
256 * @author Karsten Dambekalns <karsten@typo3.org>
258 public function flushByTag($tag) {
259 $identifiers = $this->findIdentifiersByTag($tag);
261 foreach ($identifiers as $identifier) {
262 $this->remove($identifier);
267 * Removes all cache entries of this cache which are tagged by the specified tag.
269 * @param array The tags the entries must have
271 * @author Ingo Renner <ingo@typo3.org>
273 public function flushByTags(array $tags) {
274 foreach ($tags as $tag) {
275 $this->flushByTag($tag);
280 * Returns an array with all known tags
283 * @author Karsten Dambekalns <karsten@typo3.org>
285 protected function getTagIndex() {
287 $tagIndex = apc_fetch($this->identifierPrefix
. 'tagIndex', $success);
289 return ($success ?
(array)$tagIndex : array());
293 * Saves the tags known to the backend
296 * @author Karsten Dambekalns <karsten@typo3.org>
298 protected function setTagIndex(array $tags) {
299 apc_store($this->identifierPrefix
. 'tagIndex', array_unique($tags));
303 * Adds the given tags to the tag index
307 * @author Karsten Dambekalns <karsten@typo3.org>
309 protected function addTagsToTagIndex(array $tags) {
311 $this->setTagIndex(array_merge($tags, $this->getTagIndex()));
316 * Removes the given tags from the tag index
320 * @author Karsten Dambekalns <karsten@typo3.org>
322 protected function removeTagsFromTagIndex(array $tags) {
324 $this->setTagIndex(array_diff($this->getTagIndex(), $tags));
329 * Associates the identifier with the given tags
331 * @param string $entryIdentifier
333 * @author Karsten Dambekalns <karsten@typo3.org>
334 * @author Dmitry Dulepov <dmitry.@typo3.org>
336 protected function addIdentifierToTags($entryIdentifier, array $tags) {
337 foreach ($tags as $tag) {
338 // Update tag-to-identifier index
339 $identifiers = $this->findIdentifiersByTag($tag);
340 if (array_search($entryIdentifier, $identifiers) === FALSE) {
341 $identifiers[] = $entryIdentifier;
342 apc_store($this->identifierPrefix
. 'tag_' . $tag, $identifiers);
345 // Update identifier-to-tag index
346 $existingTags = $this->findTagsByIdentifier($entryIdentifier);
347 if (array_search($entryIdentifier, $existingTags) === false) {
348 apc_store($this->identifierPrefix
. 'ident_' . $entryIdentifier, array_merge($existingTags, $tags));
355 * Removes association of the identifier with the given tags
357 * @param string $entryIdentifier
359 * @author Karsten Dambekalns <karsten@typo3.org>
360 * @author Dmitry Dulepov <dmitry.@typo3.org>
362 protected function removeIdentifierFromAllTags($entryIdentifier) {
363 // Get tags for this identifier
364 $tags = $this->findTagsByIdentifier($entryIdentifier);
365 // Deassociate tags with this identifier
366 foreach ($tags as $tag) {
367 $identifiers = $this->findIdentifiersByTag($tag);
368 // Formally array_search() below should never return false due to
369 // the behavior of findTagsByIdentifier(). But if reverse index is
370 // corrupted, we still can get 'false' from array_search(). This is
371 // not a problem because we are removing this identifier from
373 if (($key = array_search($entryIdentifier, $identifiers)) !== FALSE) {
374 unset($identifiers[$key]);
375 if (count($identifiers)) {
376 apc_store($this->identifierPrefix
. 'tag_' . $tag, $identifiers);
378 $this->removeTagsFromTagIndex(array($tag));
379 apc_delete($this->identifierPrefix
. 'tag_' . $tag);
383 // Clear reverse tag index for this identifier
384 apc_delete($this->identifierPrefix
. 'ident_' . $entryIdentifier);
388 * Does nothing, as APC does GC itself
392 public function collectGarbage() {
398 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE
]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_apcbackend.php']) {
399 include_once($TYPO3_CONF_VARS[TYPO3_MODE
]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_apcbackend.php']);