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