!!! Fixed issue #10614: update the caching framework to the latest version as in...
[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 */
71 public function __construct($options = array()) {
72 if (!extension_loaded('apc')) {
73 throw new t3lib_cache_Exception(
74 'The PHP extension "apc" must be installed and loaded in order to use the APC backend.',
75 1232985414
76 );
77 }
78
79 parent::__construct($options);
80 }
81
82 /**
83 * Saves data in the cache.
84 *
85 * Note on lifetime: the number of seconds may not exceed 2592000 (30 days),
86 * otherwise it is interpreted as a UNIX timestamp (seconds since epoch).
87 *
88 * @param string $entryIdentifier An identifier for this specific cache entry
89 * @param string $data The data to be stored
90 * @param array $tags Tags to associate with this cache entry
91 * @param integer $lifetime Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited liftime.
92 * @return void
93 * @throws t3lib_cache_Exception if no cache frontend has been set.
94 * @throws InvalidArgumentException if the identifier is not valid
95 * @throws t3lib_cache_exception_InvalidData if $data is not a string
96 * @author Christian Jul Jensen <julle@typo3.org>
97 * @author Karsten Dambekalns <karsten@typo3.org>
98 **/
99 public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
100 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
101 throw new t3lib_cache_Exception(
102 'No cache frontend has been set yet via setCache().',
103 1232986818
104 );
105 }
106
107 if (!is_string($data)) {
108 throw new t3lib_cache_exception_InvalidData(
109 'The specified data is of type "' . gettype($data) . '" but a string is expected.',
110 1232986825
111 );
112 }
113
114 $tags[] = '%APCBE%' . $this->cache->getIdentifier();
115 $expiration = $lifetime !== NULL ? $lifetime : $this->defaultLifetime;
116
117 $success = apc_store($this->identifierPrefix . $entryIdentifier, $data, $expiration);
118 if ($success === TRUE) {
119 $this->removeIdentifierFromAllTags($entryIdentifier);
120 $this->addTagsToTagIndex($tags);
121 $this->addIdentifierToTags($entryIdentifier, $tags);
122 } else {
123 throw new t3lib_cache_Exception(
124 'Could not set value. ' . $exception->getMessage(),
125 1232986877
126 );
127 }
128 }
129
130 /**
131 * Loads data from the cache.
132 *
133 * @param string $entryIdentifier An identifier which describes the cache entry to load
134 * @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
135 * @author Karsten Dambekalns <karsten@typo3.org>
136 */
137 public function get($entryIdentifier) {
138 $success = FALSE;
139 $value = apc_fetch($this->identifierPrefix . $entryIdentifier, $success);
140
141 return ($success ? $value : $success);
142 }
143
144 /**
145 * Checks if a cache entry with the specified identifier exists.
146 *
147 * @param string $entryIdentifier An identifier specifying the cache entry
148 * @return boolean TRUE if such an entry exists, FALSE if not
149 * @author Karsten Dambekalns <karsten@typo3.org>
150 */
151 public function has($entryIdentifier) {
152 $success = FALSE;
153 apc_fetch($this->identifierPrefix . $entryIdentifier, $success);
154 return $success;
155 }
156
157 /**
158 * Removes all cache entries matching the specified identifier.
159 * Usually this only affects one entry but if - for what reason ever -
160 * old entries for the identifier still exist, they are removed as well.
161 *
162 * @param string $entryIdentifier Specifies the cache entry to remove
163 * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
164 * @author Christian Jul Jensen <julle@typo3.org>
165 * @author Karsten Dambekalns <karsten@typo3.org>
166 */
167 public function remove($entryIdentifier) {
168 $this->removeIdentifierFromAllTags($entryIdentifier);
169
170 return apc_delete($this->identifierPrefix . $entryIdentifier);
171 }
172
173 /**
174 * Finds and returns all cache entry identifiers which are tagged by the
175 * specified tag.
176 *
177 * @param string $tag The tag to search for
178 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
179 * @author Karsten Dambekalns <karsten@typo3.org>
180 */
181 public function findIdentifiersByTag($tag) {
182 $success = FALSE;
183 $identifiers = apc_fetch($this->identifierPrefix . 'tag_' . $tag, $success);
184
185 if ($success === FALSE) {
186 return array();
187 } else {
188 return (array) $identifiers;
189 }
190 }
191
192 /**
193 * Finds and returns all cache entry identifiers which are tagged by the
194 * specified tags.
195 *
196 * @param array Array of tags to search for
197 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
198 * @author Ingo Renner <ingo@typo3.org>
199 */
200 public function findIdentifiersByTags(array $tags) {
201 $taggedEntries = array();
202 $foundEntries = array();
203
204 foreach ($tags as $tag) {
205 $taggedEntries[$tag] = $this->findIdentifiersByTag($tag);
206 }
207
208 $intersectedTaggedEntries = call_user_func_array('array_intersect', $taggedEntries);
209
210 foreach ($intersectedTaggedEntries as $entryIdentifier) {
211 if ($this->has($entryIdentifier)) {
212 $foundEntries[$entryIdentifier] = $entryIdentifier;
213 }
214 }
215
216 return $foundEntries;
217 }
218
219 /**
220 * Finds all tags for the given identifier. This function uses reverse tag
221 * index to search for tags.
222 *
223 * @param string $identifier Identifier to find tags by
224 * @return array Array with tags
225 * @author Karsten Dambekalns <karsten@typo3.org>
226 */
227 protected function findTagsByIdentifier($identifier) {
228 $success = FALSE;
229 $tags = apc_fetch($this->identifierPrefix . 'ident_' . $identifier, $success);
230
231 return ($success ? (array)$tags : array());
232 }
233
234 /**
235 * Removes all cache entries of this cache.
236 *
237 * @return void
238 * @author Karsten Dambekalns <karsten@typo3.org>
239 */
240 public function flush() {
241 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
242 throw new t3lib_cache_Exception(
243 'Yet no cache frontend has been set via setCache().',
244 1232986971
245 );
246 }
247
248 $this->flushByTag('%APCBE%' . $this->cache->getIdentifier());
249 }
250
251 /**
252 * Removes all cache entries of this cache which are tagged by the specified
253 * tag.
254 *
255 * @param string $tag The tag the entries must have
256 * @return void
257 * @author Karsten Dambekalns <karsten@typo3.org>
258 */
259 public function flushByTag($tag) {
260 $identifiers = $this->findIdentifiersByTag($tag);
261
262 foreach ($identifiers as $identifier) {
263 $this->remove($identifier);
264 }
265 }
266
267 /**
268 * Removes all cache entries of this cache which are tagged by the specified tag.
269 *
270 * @param array The tags the entries must have
271 * @return void
272 * @author Ingo Renner <ingo@typo3.org>
273 */
274 public function flushByTags(array $tags) {
275 foreach ($tags as $tag) {
276 $this->flushByTag($tag);
277 }
278 }
279
280 /**
281 * Returns an array with all known tags
282 *
283 * @return array
284 * @author Karsten Dambekalns <karsten@typo3.org>
285 */
286 protected function getTagIndex() {
287 $success = FALSE;
288 $tagIndex = apc_fetch($this->identifierPrefix . 'tagIndex', $success);
289
290 return ($success ? (array)$tagIndex : array());
291 }
292
293 /**
294 * Saves the tags known to the backend
295 *
296 * @param array $tags
297 * @author Karsten Dambekalns <karsten@typo3.org>
298 */
299 protected function setTagIndex(array $tags) {
300 apc_store($this->identifierPrefix . 'tagIndex', array_unique($tags));
301 }
302
303 /**
304 * Adds the given tags to the tag index
305 *
306 * @param array $tags
307 * @return void
308 * @author Karsten Dambekalns <karsten@typo3.org>
309 */
310 protected function addTagsToTagIndex(array $tags) {
311 if (count($tags)) {
312 $this->setTagIndex(array_merge($tags, $this->getTagIndex()));
313 }
314 }
315
316 /**
317 * Removes the given tags from the tag index
318 *
319 * @param array $tags
320 * @return void
321 * @author Karsten Dambekalns <karsten@typo3.org>
322 */
323 protected function removeTagsFromTagIndex(array $tags) {
324 if (count($tags)) {
325 $this->setTagIndex(array_diff($this->getTagIndex(), $tags));
326 }
327 }
328
329 /**
330 * Associates the identifier with the given tags
331 *
332 * @param string $entryIdentifier
333 * @param array $tags
334 * @author Karsten Dambekalns <karsten@typo3.org>
335 */
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);
343 }
344
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));
349 }
350
351 }
352 }
353
354 /**
355 * Removes association of the identifier with the given tags
356 *
357 * @param string $entryIdentifier
358 * @param array $tags
359 * @author Karsten Dambekalns <karsten@typo3.org>
360 */
361 protected function removeIdentifierFromAllTags($entryIdentifier) {
362 // Get tags for this identifier
363 $tags = $this->findTagsByIdentifier($entryIdentifier);
364 // Deassociate tags with this identifier
365 foreach ($tags as $tag) {
366 $identifiers = $this->findIdentifiersByTag($tag);
367 // Formally array_search() below should never return false due to
368 // the behavior of findTagsByIdentifier(). But if reverse index is
369 // corrupted, we still can get 'false' from array_search(). This is
370 // not a problem because we are removing this identifier from
371 // anywhere.
372 if (($key = array_search($entryIdentifier, $identifiers)) !== FALSE) {
373 unset($identifiers[$key]);
374 if (count($identifiers)) {
375 apc_store($this->identifierPrefix . 'tag_' . $tag, $identifiers);
376 } else {
377 $this->removeTagsFromTagIndex(array($tag));
378 apc_delete($this->identifierPrefix . 'tag_' . $tag);
379 }
380 }
381 }
382 // Clear reverse tag index for this identifier
383 apc_delete($this->identifierPrefix . 'ident_' . $entryIdentifier);
384 }
385
386 /**
387 * Does nothing, as APC does GC itself
388 *
389 * @return void
390 */
391 public function collectGarbage() {
392
393 }
394 }
395
396
397 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_apcbackend.php']) {
398 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_apcbackend.php']);
399 }
400
401 ?>