[TASK] Detect APC and APCu correctly
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Cache / Backend / ApcBackend.php
1 <?php
2 namespace TYPO3\CMS\Core\Cache\Backend;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2009-2011 Ingo Renner <ingo@typo3.org>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 *
19 * This script is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * This copyright notice MUST APPEAR in all copies of the script!
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 * @author Robert Lemke <robert@typo3.org>
49 * @author Karsten Dambekalns <karsten@typo3.org>
50 * @author Christian Jul Jensen <julle@typo3.org>
51 * @author Dmitry Dulepov <dmitry@typo3.org>
52 * @api
53 */
54 class ApcBackend extends \TYPO3\CMS\Core\Cache\Backend\AbstractBackend implements \TYPO3\CMS\Core\Cache\Backend\TaggableBackendInterface {
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 * Set the cache identifier prefix.
65 *
66 * @param string $identifierPrefix
67 */
68 protected function setIdentifierPrefix($identifierPrefix) {
69 $this->identifierPrefix = $identifierPrefix;
70 }
71
72 /**
73 * Retrieves the cache identifier prefix.
74 *
75 * @return string
76 */
77 protected function getIdentifierPrefix() {
78 return $this->identifierPrefix;
79 }
80
81 /**
82 * Constructs this backend
83 *
84 * @param string $context FLOW3's application context
85 * @param array $options Configuration options - unused here
86 * @throws \TYPO3\CMS\Core\Cache\Exception
87 */
88 public function __construct($context, array $options = array()) {
89 if (!extension_loaded('apc')) {
90 throw new \TYPO3\CMS\Core\Cache\Exception('The PHP extension "apc" or "apcu" must be installed and loaded in order to use the APC backend.', 1232985414);
91 }
92 parent::__construct($context, $options);
93 }
94
95 /**
96 * Initializes the identifier prefix when setting the cache.
97 *
98 * @param \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache
99 * @return void
100 */
101 public function setCache(\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache) {
102 parent::setCache($cache);
103 $processUser = $this->getCurrentUserData();
104 $pathHash = \TYPO3\CMS\Core\Utility\GeneralUtility::shortMD5($this->getPathSite() . $processUser['name'] . $this->context . $cache->getIdentifier(), 12);
105 $this->setIdentifierPrefix('TYPO3_' . $pathHash);
106 }
107
108 /**
109 * Returns the current user data with posix_getpwuid or a default structure when
110 * posix_getpwuid is not available.
111 *
112 * @return array
113 */
114 protected function getCurrentUserData() {
115 return extension_loaded('posix') ? posix_getpwuid(posix_geteuid()) : array('name' => 'default');
116 }
117
118 /**
119 * Returns the PATH_site constant.
120 *
121 * @return string
122 */
123 protected function getPathSite() {
124 return PATH_site;
125 }
126
127 /**
128 * Saves data in the cache.
129 *
130 * @param string $entryIdentifier An identifier for this specific cache entry
131 * @param string $data The data to be stored
132 * @param array $tags Tags to associate with this cache entry
133 * @param integer $lifetime Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited liftime.
134 * @return void
135 * @throws \TYPO3\CMS\Core\Cache\Exception if no cache frontend has been set.
136 * @throws \TYPO3\CMS\Core\Cache\Exception\InvalidDataException if $data is not a string
137 * @api
138 */
139 public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
140 if (!$this->cache instanceof \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface) {
141 throw new \TYPO3\CMS\Core\Cache\Exception('No cache frontend has been set yet via setCache().', 1232986818);
142 }
143 if (!is_string($data)) {
144 throw new \TYPO3\CMS\Core\Cache\Exception\InvalidDataException('The specified data is of type "' . gettype($data) . '" but a string is expected.', 1232986825);
145 }
146 $tags[] = '%APCBE%' . $this->cacheIdentifier;
147 $expiration = $lifetime !== NULL ? $lifetime : $this->defaultLifetime;
148 $success = apc_store($this->getIdentifierPrefix() . $entryIdentifier, $data, $expiration);
149 if ($success === TRUE) {
150 $this->removeIdentifierFromAllTags($entryIdentifier);
151 $this->addIdentifierToTags($entryIdentifier, $tags);
152 } else {
153 throw new \TYPO3\CMS\Core\Cache\Exception('Could not set value.', 1232986877);
154 }
155 }
156
157 /**
158 * Loads data from the cache.
159 *
160 * @param string $entryIdentifier An identifier which describes the cache entry to load
161 * @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
162 * @api
163 */
164 public function get($entryIdentifier) {
165 $success = FALSE;
166 $value = apc_fetch($this->getIdentifierPrefix() . $entryIdentifier, $success);
167 return $success ? $value : $success;
168 }
169
170 /**
171 * Checks if a cache entry with the specified identifier exists.
172 *
173 * @param string $entryIdentifier An identifier specifying the cache entry
174 * @return boolean TRUE if such an entry exists, FALSE if not
175 * @api
176 */
177 public function has($entryIdentifier) {
178 $success = FALSE;
179 apc_fetch($this->getIdentifierPrefix() . $entryIdentifier, $success);
180 return $success;
181 }
182
183 /**
184 * Removes all cache entries matching the specified identifier.
185 * Usually this only affects one entry but if - for what reason ever -
186 * old entries for the identifier still exist, they are removed as well.
187 *
188 * @param string $entryIdentifier Specifies the cache entry to remove
189 * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
190 * @api
191 */
192 public function remove($entryIdentifier) {
193 $this->removeIdentifierFromAllTags($entryIdentifier);
194 return apc_delete($this->getIdentifierPrefix() . $entryIdentifier);
195 }
196
197 /**
198 * Finds and returns all cache entry identifiers which are tagged by the
199 * specified tag.
200 *
201 * @param string $tag The tag to search for
202 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
203 * @api
204 */
205 public function findIdentifiersByTag($tag) {
206 $success = FALSE;
207 $identifiers = apc_fetch($this->getIdentifierPrefix() . 'tag_' . $tag, $success);
208 if ($success === FALSE) {
209 return array();
210 } else {
211 return (array) $identifiers;
212 }
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 */
222 protected function findTagsByIdentifier($identifier) {
223 $success = FALSE;
224 $tags = apc_fetch($this->getIdentifierPrefix() . 'ident_' . $identifier, $success);
225 return $success ? (array) $tags : array();
226 }
227
228 /**
229 * Removes all cache entries of this cache.
230 *
231 * @return void
232 * @throws \TYPO3\CMS\Core\Cache\Exception
233 * @api
234 */
235 public function flush() {
236 if (!$this->cache instanceof \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface) {
237 throw new \TYPO3\CMS\Core\Cache\Exception('Yet no cache frontend has been set via setCache().', 1232986971);
238 }
239 $this->flushByTag('%APCBE%' . $this->cacheIdentifier);
240 }
241
242 /**
243 * Removes all cache entries of this cache which are tagged by the specified tag.
244 *
245 * @param string $tag The tag the entries must have
246 * @return void
247 * @api
248 */
249 public function flushByTag($tag) {
250 $identifiers = $this->findIdentifiersByTag($tag);
251 foreach ($identifiers as $identifier) {
252 $this->remove($identifier);
253 }
254 }
255
256 /**
257 * Associates the identifier with the given tags
258 *
259 * @param string $entryIdentifier
260 * @param array $tags
261 * @return void
262 */
263 protected function addIdentifierToTags($entryIdentifier, array $tags) {
264 foreach ($tags as $tag) {
265 // Update tag-to-identifier index
266 $identifiers = $this->findIdentifiersByTag($tag);
267 if (array_search($entryIdentifier, $identifiers) === FALSE) {
268 $identifiers[] = $entryIdentifier;
269 apc_store($this->getIdentifierPrefix() . 'tag_' . $tag, $identifiers);
270 }
271 // Update identifier-to-tag index
272 $existingTags = $this->findTagsByIdentifier($entryIdentifier);
273 if (array_search($entryIdentifier, $existingTags) === FALSE) {
274 apc_store($this->getIdentifierPrefix() . 'ident_' . $entryIdentifier, array_merge($existingTags, $tags));
275 }
276 }
277 }
278
279 /**
280 * Removes association of the identifier with the given tags
281 *
282 * @param string $entryIdentifier
283 * @return void
284 */
285 protected function removeIdentifierFromAllTags($entryIdentifier) {
286 // Get tags for this identifier
287 $tags = $this->findTagsByIdentifier($entryIdentifier);
288 // Deassociate tags with this identifier
289 foreach ($tags as $tag) {
290 $identifiers = $this->findIdentifiersByTag($tag);
291 // Formally array_search() below should never return FALSE due to
292 // the behavior of findTagsByIdentifier(). But if reverse index is
293 // corrupted, we still can get 'FALSE' from array_search(). This is
294 // not a problem because we are removing this identifier from
295 // anywhere.
296 if (($key = array_search($entryIdentifier, $identifiers)) !== FALSE) {
297 unset($identifiers[$key]);
298 if (count($identifiers)) {
299 apc_store($this->getIdentifierPrefix() . 'tag_' . $tag, $identifiers);
300 } else {
301 apc_delete($this->getIdentifierPrefix() . 'tag_' . $tag);
302 }
303 }
304 }
305 // Clear reverse tag index for this identifier
306 apc_delete($this->getIdentifierPrefix() . 'ident_' . $entryIdentifier);
307 }
308
309 /**
310 * Does nothing, as APC does GC itself
311 *
312 * @return void
313 */
314 public function collectGarbage() {
315
316 }
317
318 }
319
320
321 ?>