Fixed bug #11326: Memcached backend does not work well if cache of a page shall expir...
[Packages/TYPO3.CMS.git] / t3lib / cache / backend / class.t3lib_cache_backend_apcbackend.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2009 Ingo Renner <ingo@typo3.org>
6 * All rights reserved
7 *
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.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 *
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.
21 *
22 * This copyright notice MUST APPEAR in all copies of the script!
23 ***************************************************************/
24
25
26 /**
27 * A caching backend which stores cache entries by using APC.
28 *
29 * This backend uses the following types of keys:
30 * - tag_xxx
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)
34 * - ident_xxx
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
38 * - tagIndex
39 * Value is a List of all tags (array)
40
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:
43 * - "TYPO3"
44 * - MD5 of script path and filename and SAPI name
45 * This prefix makes sure that keys from the different installations do not
46 * conflict.
47 *
48 * This file is a backport from FLOW3
49 *
50 * @package TYPO3
51 * @subpackage t3lib_cache
52 * @version $Id$
53 */
54 class t3lib_cache_backend_ApcBackend extends t3lib_cache_backend_AbstractBackend {
55
56 /**
57 * A prefix to seperate stored data from other data possible stored in the APC
58 *
59 * @var string
60 */
61 protected $identifierPrefix;
62
63 /**
64 * Constructs this backend
65 *
66 * @param mixed $options Configuration options - unused here
67 * @author Robert Lemke <robert@typo3.org>
68 * @author Karsten Dambekalns <karsten@typo3.org>
69 */
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.',
74 1232985414
75 );
76 }
77
78 parent::__construct($options);
79 }
80
81 /**
82 * Saves data in the cache.
83 *
84 * @param string $entryIdentifier An identifier for this specific cache entry
85 * @param string $data The data to be stored
86 * @param array $tags Tags to associate with this cache entry
87 * @param integer $lifetime Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited liftime.
88 * @return void
89 * @throws t3lib_cache_Exception if no cache frontend has been set.
90 * @throws InvalidArgumentException if the identifier is not valid
91 * @throws t3lib_cache_exception_InvalidData if $data is not a string
92 * @author Christian Jul Jensen <julle@typo3.org>
93 * @author Karsten Dambekalns <karsten@typo3.org>
94 */
95 public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
96 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
97 throw new t3lib_cache_Exception(
98 'No cache frontend has been set yet via setCache().',
99 1232986818
100 );
101 }
102
103 if (!is_string($data)) {
104 throw new t3lib_cache_exception_InvalidData(
105 'The specified data is of type "' . gettype($data) . '" but a string is expected.',
106 1232986825
107 );
108 }
109
110 $tags[] = '%APCBE%' . $this->cache->getIdentifier();
111 $expiration = $lifetime !== NULL ? $lifetime : $this->defaultLifetime;
112
113 $success = apc_store($this->identifierPrefix . $entryIdentifier, $data, $expiration);
114 if ($success === TRUE) {
115 $this->removeIdentifierFromAllTags($entryIdentifier);
116 $this->addTagsToTagIndex($tags);
117 $this->addIdentifierToTags($entryIdentifier, $tags);
118 } else {
119 throw new t3lib_cache_Exception(
120 'Could not set value.',
121 1232986877
122 );
123 }
124 }
125
126 /**
127 * Loads data from the cache.
128 *
129 * @param string $entryIdentifier An identifier which describes the cache entry to load
130 * @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
131 * @author Karsten Dambekalns <karsten@typo3.org>
132 */
133 public function get($entryIdentifier) {
134 $success = FALSE;
135 $value = apc_fetch($this->identifierPrefix . $entryIdentifier, $success);
136
137 return ($success ? $value : $success);
138 }
139
140 /**
141 * Checks if a cache entry with the specified identifier exists.
142 *
143 * @param string $entryIdentifier An identifier specifying the cache entry
144 * @return boolean TRUE if such an entry exists, FALSE if not
145 * @author Karsten Dambekalns <karsten@typo3.org>
146 */
147 public function has($entryIdentifier) {
148 $success = FALSE;
149 apc_fetch($this->identifierPrefix . $entryIdentifier, $success);
150 return $success;
151 }
152
153 /**
154 * Removes all cache entries matching the specified identifier.
155 * Usually this only affects one entry but if - for what reason ever -
156 * old entries for the identifier still exist, they are removed as well.
157 *
158 * @param string $entryIdentifier Specifies the cache entry to remove
159 * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
160 * @author Christian Jul Jensen <julle@typo3.org>
161 * @author Karsten Dambekalns <karsten@typo3.org>
162 */
163 public function remove($entryIdentifier) {
164 $this->removeIdentifierFromAllTags($entryIdentifier);
165
166 return apc_delete($this->identifierPrefix . $entryIdentifier);
167 }
168
169 /**
170 * Finds and returns all cache entry identifiers which are tagged by the
171 * specified tag.
172 *
173 * @param string $tag The tag to search for
174 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
175 * @author Karsten Dambekalns <karsten@typo3.org>
176 */
177 public function findIdentifiersByTag($tag) {
178 $success = FALSE;
179 $identifiers = apc_fetch($this->identifierPrefix . 'tag_' . $tag, $success);
180
181 if ($success === FALSE) {
182 return array();
183 } else {
184 return (array) $identifiers;
185 }
186 }
187
188 /**
189 * Finds and returns all cache entry identifiers which are tagged by the
190 * specified tags.
191 *
192 * @param array Array of tags to search for
193 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
194 * @author Ingo Renner <ingo@typo3.org>
195 */
196 public function findIdentifiersByTags(array $tags) {
197 $taggedEntries = array();
198 $foundEntries = array();
199
200 foreach ($tags as $tag) {
201 $taggedEntries[$tag] = $this->findIdentifiersByTag($tag);
202 }
203
204 $intersectedTaggedEntries = call_user_func_array('array_intersect', $taggedEntries);
205
206 foreach ($intersectedTaggedEntries as $entryIdentifier) {
207 if ($this->has($entryIdentifier)) {
208 $foundEntries[$entryIdentifier] = $entryIdentifier;
209 }
210 }
211
212 return $foundEntries;
213 }
214
215 /**
216 * Finds all tags for the given identifier. This function uses reverse tag
217 * index to search for tags.
218 *
219 * @param string $identifier Identifier to find tags by
220 * @return array Array with tags
221 * @author Karsten Dambekalns <karsten@typo3.org>
222 */
223 protected function findTagsByIdentifier($identifier) {
224 $success = FALSE;
225 $tags = apc_fetch($this->identifierPrefix . 'ident_' . $identifier, $success);
226
227 return ($success ? (array)$tags : array());
228 }
229
230 /**
231 * Removes all cache entries of this cache.
232 *
233 * @return void
234 * @author Karsten Dambekalns <karsten@typo3.org>
235 */
236 public function flush() {
237 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
238 throw new t3lib_cache_Exception(
239 'Yet no cache frontend has been set via setCache().',
240 1232986971
241 );
242 }
243
244 $this->flushByTag('%APCBE%' . $this->cache->getIdentifier());
245 }
246
247 /**
248 * Removes all cache entries of this cache which are tagged by the specified
249 * tag.
250 *
251 * @param string $tag The tag the entries must have
252 * @return void
253 * @author Karsten Dambekalns <karsten@typo3.org>
254 */
255 public function flushByTag($tag) {
256 $identifiers = $this->findIdentifiersByTag($tag);
257
258 foreach ($identifiers as $identifier) {
259 $this->remove($identifier);
260 }
261 }
262
263 /**
264 * Removes all cache entries of this cache which are tagged by the specified tag.
265 *
266 * @param array The tags the entries must have
267 * @return void
268 * @author Ingo Renner <ingo@typo3.org>
269 */
270 public function flushByTags(array $tags) {
271 foreach ($tags as $tag) {
272 $this->flushByTag($tag);
273 }
274 }
275
276 /**
277 * Returns an array with all known tags
278 *
279 * @return array
280 * @author Karsten Dambekalns <karsten@typo3.org>
281 */
282 protected function getTagIndex() {
283 $success = FALSE;
284 $tagIndex = apc_fetch($this->identifierPrefix . 'tagIndex', $success);
285
286 return ($success ? (array)$tagIndex : array());
287 }
288
289 /**
290 * Saves the tags known to the backend
291 *
292 * @param array $tags
293 * @author Karsten Dambekalns <karsten@typo3.org>
294 */
295 protected function setTagIndex(array $tags) {
296 apc_store($this->identifierPrefix . 'tagIndex', array_unique($tags));
297 }
298
299 /**
300 * Adds the given tags to the tag index
301 *
302 * @param array $tags
303 * @return void
304 * @author Karsten Dambekalns <karsten@typo3.org>
305 */
306 protected function addTagsToTagIndex(array $tags) {
307 if (count($tags)) {
308 $this->setTagIndex(array_merge($tags, $this->getTagIndex()));
309 }
310 }
311
312 /**
313 * Removes the given tags from the tag index
314 *
315 * @param array $tags
316 * @return void
317 * @author Karsten Dambekalns <karsten@typo3.org>
318 */
319 protected function removeTagsFromTagIndex(array $tags) {
320 if (count($tags)) {
321 $this->setTagIndex(array_diff($this->getTagIndex(), $tags));
322 }
323 }
324
325 /**
326 * Associates the identifier with the given tags
327 *
328 * @param string $entryIdentifier
329 * @param array $tags
330 * @author Karsten Dambekalns <karsten@typo3.org>
331 * @author Dmitry Dulepov <dmitry.@typo3.org>
332 */
333 protected function addIdentifierToTags($entryIdentifier, array $tags) {
334 foreach ($tags as $tag) {
335 // Update tag-to-identifier index
336 $identifiers = $this->findIdentifiersByTag($tag);
337 if (array_search($entryIdentifier, $identifiers) === FALSE) {
338 $identifiers[] = $entryIdentifier;
339 apc_store($this->identifierPrefix . 'tag_' . $tag, $identifiers);
340 }
341
342 // Update identifier-to-tag index
343 $existingTags = $this->findTagsByIdentifier($entryIdentifier);
344 if (array_search($entryIdentifier, $existingTags) === false) {
345 apc_store($this->identifierPrefix . 'ident_' . $entryIdentifier, array_merge($existingTags, $tags));
346 }
347
348 }
349 }
350
351 /**
352 * Removes association of the identifier with the given tags
353 *
354 * @param string $entryIdentifier
355 * @param array $tags
356 * @author Karsten Dambekalns <karsten@typo3.org>
357 * @author Dmitry Dulepov <dmitry.@typo3.org>
358 */
359 protected function removeIdentifierFromAllTags($entryIdentifier) {
360 // Get tags for this identifier
361 $tags = $this->findTagsByIdentifier($entryIdentifier);
362 // Deassociate tags with this identifier
363 foreach ($tags as $tag) {
364 $identifiers = $this->findIdentifiersByTag($tag);
365 // Formally array_search() below should never return false due to
366 // the behavior of findTagsByIdentifier(). But if reverse index is
367 // corrupted, we still can get 'false' from array_search(). This is
368 // not a problem because we are removing this identifier from
369 // anywhere.
370 if (($key = array_search($entryIdentifier, $identifiers)) !== FALSE) {
371 unset($identifiers[$key]);
372 if (count($identifiers)) {
373 apc_store($this->identifierPrefix . 'tag_' . $tag, $identifiers);
374 } else {
375 $this->removeTagsFromTagIndex(array($tag));
376 apc_delete($this->identifierPrefix . 'tag_' . $tag);
377 }
378 }
379 }
380 // Clear reverse tag index for this identifier
381 apc_delete($this->identifierPrefix . 'ident_' . $entryIdentifier);
382 }
383
384 /**
385 * Does nothing, as APC does GC itself
386 *
387 * @return void
388 */
389 public function collectGarbage() {
390
391 }
392 }
393
394
395 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_apcbackend.php']) {
396 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_apcbackend.php']);
397 }
398
399 ?>