Updated the caching framework to the according FLOW3 revision 2616
[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 * 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).
86 *
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.
91 * @return void
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>
97 */
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().',
102 1232986818
103 );
104 }
105
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.',
109 1232986825
110 );
111 }
112
113 $tags[] = '%APCBE%' . $this->cache->getIdentifier();
114 $expiration = $lifetime !== NULL ? $lifetime : $this->defaultLifetime;
115
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);
121 } else {
122 throw new t3lib_cache_Exception(
123 'Could not set value.',
124 1232986877
125 );
126 }
127 }
128
129 /**
130 * Loads data from the cache.
131 *
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>
135 */
136 public function get($entryIdentifier) {
137 $success = FALSE;
138 $value = apc_fetch($this->identifierPrefix . $entryIdentifier, $success);
139
140 return ($success ? $value : $success);
141 }
142
143 /**
144 * Checks if a cache entry with the specified identifier exists.
145 *
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>
149 */
150 public function has($entryIdentifier) {
151 $success = FALSE;
152 apc_fetch($this->identifierPrefix . $entryIdentifier, $success);
153 return $success;
154 }
155
156 /**
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.
160 *
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>
165 */
166 public function remove($entryIdentifier) {
167 $this->removeIdentifierFromAllTags($entryIdentifier);
168
169 return apc_delete($this->identifierPrefix . $entryIdentifier);
170 }
171
172 /**
173 * Finds and returns all cache entry identifiers which are tagged by the
174 * specified tag.
175 *
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>
179 */
180 public function findIdentifiersByTag($tag) {
181 $success = FALSE;
182 $identifiers = apc_fetch($this->identifierPrefix . 'tag_' . $tag, $success);
183
184 if ($success === FALSE) {
185 return array();
186 } else {
187 return (array) $identifiers;
188 }
189 }
190
191 /**
192 * Finds and returns all cache entry identifiers which are tagged by the
193 * specified tags.
194 *
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>
198 */
199 public function findIdentifiersByTags(array $tags) {
200 $taggedEntries = array();
201 $foundEntries = array();
202
203 foreach ($tags as $tag) {
204 $taggedEntries[$tag] = $this->findIdentifiersByTag($tag);
205 }
206
207 $intersectedTaggedEntries = call_user_func_array('array_intersect', $taggedEntries);
208
209 foreach ($intersectedTaggedEntries as $entryIdentifier) {
210 if ($this->has($entryIdentifier)) {
211 $foundEntries[$entryIdentifier] = $entryIdentifier;
212 }
213 }
214
215 return $foundEntries;
216 }
217
218 /**
219 * Finds all tags for the given identifier. This function uses reverse tag
220 * index to search for tags.
221 *
222 * @param string $identifier Identifier to find tags by
223 * @return array Array with tags
224 * @author Karsten Dambekalns <karsten@typo3.org>
225 */
226 protected function findTagsByIdentifier($identifier) {
227 $success = FALSE;
228 $tags = apc_fetch($this->identifierPrefix . 'ident_' . $identifier, $success);
229
230 return ($success ? (array)$tags : array());
231 }
232
233 /**
234 * Removes all cache entries of this cache.
235 *
236 * @return void
237 * @author Karsten Dambekalns <karsten@typo3.org>
238 */
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().',
243 1232986971
244 );
245 }
246
247 $this->flushByTag('%APCBE%' . $this->cache->getIdentifier());
248 }
249
250 /**
251 * Removes all cache entries of this cache which are tagged by the specified
252 * tag.
253 *
254 * @param string $tag The tag the entries must have
255 * @return void
256 * @author Karsten Dambekalns <karsten@typo3.org>
257 */
258 public function flushByTag($tag) {
259 $identifiers = $this->findIdentifiersByTag($tag);
260
261 foreach ($identifiers as $identifier) {
262 $this->remove($identifier);
263 }
264 }
265
266 /**
267 * Removes all cache entries of this cache which are tagged by the specified tag.
268 *
269 * @param array The tags the entries must have
270 * @return void
271 * @author Ingo Renner <ingo@typo3.org>
272 */
273 public function flushByTags(array $tags) {
274 foreach ($tags as $tag) {
275 $this->flushByTag($tag);
276 }
277 }
278
279 /**
280 * Returns an array with all known tags
281 *
282 * @return array
283 * @author Karsten Dambekalns <karsten@typo3.org>
284 */
285 protected function getTagIndex() {
286 $success = FALSE;
287 $tagIndex = apc_fetch($this->identifierPrefix . 'tagIndex', $success);
288
289 return ($success ? (array)$tagIndex : array());
290 }
291
292 /**
293 * Saves the tags known to the backend
294 *
295 * @param array $tags
296 * @author Karsten Dambekalns <karsten@typo3.org>
297 */
298 protected function setTagIndex(array $tags) {
299 apc_store($this->identifierPrefix . 'tagIndex', array_unique($tags));
300 }
301
302 /**
303 * Adds the given tags to the tag index
304 *
305 * @param array $tags
306 * @return void
307 * @author Karsten Dambekalns <karsten@typo3.org>
308 */
309 protected function addTagsToTagIndex(array $tags) {
310 if (count($tags)) {
311 $this->setTagIndex(array_merge($tags, $this->getTagIndex()));
312 }
313 }
314
315 /**
316 * Removes the given tags from the tag index
317 *
318 * @param array $tags
319 * @return void
320 * @author Karsten Dambekalns <karsten@typo3.org>
321 */
322 protected function removeTagsFromTagIndex(array $tags) {
323 if (count($tags)) {
324 $this->setTagIndex(array_diff($this->getTagIndex(), $tags));
325 }
326 }
327
328 /**
329 * Associates the identifier with the given tags
330 *
331 * @param string $entryIdentifier
332 * @param array $tags
333 * @author Karsten Dambekalns <karsten@typo3.org>
334 * @author Dmitry Dulepov <dmitry.@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 * @author Dmitry Dulepov <dmitry.@typo3.org>
361 */
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
372 // anywhere.
373 if (($key = array_search($entryIdentifier, $identifiers)) !== FALSE) {
374 unset($identifiers[$key]);
375 if (count($identifiers)) {
376 apc_store($this->identifierPrefix . 'tag_' . $tag, $identifiers);
377 } else {
378 $this->removeTagsFromTagIndex(array($tag));
379 apc_delete($this->identifierPrefix . 'tag_' . $tag);
380 }
381 }
382 }
383 // Clear reverse tag index for this identifier
384 apc_delete($this->identifierPrefix . 'ident_' . $entryIdentifier);
385 }
386
387 /**
388 * Does nothing, as APC does GC itself
389 *
390 * @return void
391 */
392 public function collectGarbage() {
393
394 }
395 }
396
397
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']);
400 }
401
402 ?>