[BUGFIX] Warn about apc.enable_cli=0 on command line
[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-2013 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 if (PHP_SAPI === 'cli' && ini_get('apc.enable_cli') == 0) {
93 throw new \TYPO3\CMS\Core\Cache\Exception('The APC backend cannot be used because apc is disabled on CLI.', 1232985415);
94 }
95 parent::__construct($context, $options);
96 }
97
98 /**
99 * Initializes the identifier prefix when setting the cache.
100 *
101 * @param \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache
102 * @return void
103 */
104 public function setCache(\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache) {
105 parent::setCache($cache);
106 $processUser = $this->getCurrentUserData();
107 $pathHash = \TYPO3\CMS\Core\Utility\GeneralUtility::shortMD5($this->getPathSite() . $processUser['name'] . $this->context . $cache->getIdentifier(), 12);
108 $this->setIdentifierPrefix('TYPO3_' . $pathHash);
109 }
110
111 /**
112 * Returns the current user data with posix_getpwuid or a default structure when
113 * posix_getpwuid is not available.
114 *
115 * @return array
116 */
117 protected function getCurrentUserData() {
118 return extension_loaded('posix') ? posix_getpwuid(posix_geteuid()) : array('name' => 'default');
119 }
120
121 /**
122 * Returns the PATH_site constant.
123 *
124 * @return string
125 */
126 protected function getPathSite() {
127 return PATH_site;
128 }
129
130 /**
131 * Saves data in the cache.
132 *
133 * @param string $entryIdentifier An identifier for this specific cache entry
134 * @param string $data The data to be stored
135 * @param array $tags Tags to associate with this cache entry
136 * @param integer $lifetime Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited liftime.
137 * @return void
138 * @throws \TYPO3\CMS\Core\Cache\Exception if no cache frontend has been set.
139 * @throws \TYPO3\CMS\Core\Cache\Exception\InvalidDataException if $data is not a string
140 * @api
141 */
142 public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
143 if (!$this->cache instanceof \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface) {
144 throw new \TYPO3\CMS\Core\Cache\Exception('No cache frontend has been set yet via setCache().', 1232986818);
145 }
146 if (!is_string($data)) {
147 throw new \TYPO3\CMS\Core\Cache\Exception\InvalidDataException('The specified data is of type "' . gettype($data) . '" but a string is expected.', 1232986825);
148 }
149 $tags[] = '%APCBE%' . $this->cacheIdentifier;
150 $expiration = $lifetime !== NULL ? $lifetime : $this->defaultLifetime;
151 $success = apc_store($this->getIdentifierPrefix() . $entryIdentifier, $data, $expiration);
152 if ($success === TRUE) {
153 $this->removeIdentifierFromAllTags($entryIdentifier);
154 $this->addIdentifierToTags($entryIdentifier, $tags);
155 } else {
156 throw new \TYPO3\CMS\Core\Cache\Exception('Could not set value.', 1232986877);
157 }
158 }
159
160 /**
161 * Loads data from the cache.
162 *
163 * @param string $entryIdentifier An identifier which describes the cache entry to load
164 * @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
165 * @api
166 */
167 public function get($entryIdentifier) {
168 $success = FALSE;
169 $value = apc_fetch($this->getIdentifierPrefix() . $entryIdentifier, $success);
170 return $success ? $value : $success;
171 }
172
173 /**
174 * Checks if a cache entry with the specified identifier exists.
175 *
176 * @param string $entryIdentifier An identifier specifying the cache entry
177 * @return boolean TRUE if such an entry exists, FALSE if not
178 * @api
179 */
180 public function has($entryIdentifier) {
181 $success = FALSE;
182 apc_fetch($this->getIdentifierPrefix() . $entryIdentifier, $success);
183 return $success;
184 }
185
186 /**
187 * Removes all cache entries matching the specified identifier.
188 * Usually this only affects one entry but if - for what reason ever -
189 * old entries for the identifier still exist, they are removed as well.
190 *
191 * @param string $entryIdentifier Specifies the cache entry to remove
192 * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
193 * @api
194 */
195 public function remove($entryIdentifier) {
196 $this->removeIdentifierFromAllTags($entryIdentifier);
197 return apc_delete($this->getIdentifierPrefix() . $entryIdentifier);
198 }
199
200 /**
201 * Finds and returns all cache entry identifiers which are tagged by the
202 * specified tag.
203 *
204 * @param string $tag The tag to search for
205 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
206 * @api
207 */
208 public function findIdentifiersByTag($tag) {
209 $success = FALSE;
210 $identifiers = apc_fetch($this->getIdentifierPrefix() . 'tag_' . $tag, $success);
211 if ($success === FALSE) {
212 return array();
213 } else {
214 return (array) $identifiers;
215 }
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 */
225 protected function findTagsByIdentifier($identifier) {
226 $success = FALSE;
227 $tags = apc_fetch($this->getIdentifierPrefix() . 'ident_' . $identifier, $success);
228 return $success ? (array) $tags : array();
229 }
230
231 /**
232 * Removes all cache entries of this cache.
233 *
234 * @return void
235 * @throws \TYPO3\CMS\Core\Cache\Exception
236 * @api
237 */
238 public function flush() {
239 if (!$this->cache instanceof \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface) {
240 throw new \TYPO3\CMS\Core\Cache\Exception('Yet no cache frontend has been set via setCache().', 1232986971);
241 }
242 $this->flushByTag('%APCBE%' . $this->cacheIdentifier);
243 }
244
245 /**
246 * Removes all cache entries of this cache which are tagged by the specified tag.
247 *
248 * @param string $tag The tag the entries must have
249 * @return void
250 * @api
251 */
252 public function flushByTag($tag) {
253 $identifiers = $this->findIdentifiersByTag($tag);
254 foreach ($identifiers as $identifier) {
255 $this->remove($identifier);
256 }
257 }
258
259 /**
260 * Associates the identifier with the given tags
261 *
262 * @param string $entryIdentifier
263 * @param array $tags
264 * @return void
265 */
266 protected function addIdentifierToTags($entryIdentifier, array $tags) {
267 foreach ($tags as $tag) {
268 // Update tag-to-identifier index
269 $identifiers = $this->findIdentifiersByTag($tag);
270 if (array_search($entryIdentifier, $identifiers) === FALSE) {
271 $identifiers[] = $entryIdentifier;
272 apc_store($this->getIdentifierPrefix() . 'tag_' . $tag, $identifiers);
273 }
274 // Update identifier-to-tag index
275 $existingTags = $this->findTagsByIdentifier($entryIdentifier);
276 if (array_search($entryIdentifier, $existingTags) === FALSE) {
277 apc_store($this->getIdentifierPrefix() . 'ident_' . $entryIdentifier, array_merge($existingTags, $tags));
278 }
279 }
280 }
281
282 /**
283 * Removes association of the identifier with the given tags
284 *
285 * @param string $entryIdentifier
286 * @return void
287 */
288 protected function removeIdentifierFromAllTags($entryIdentifier) {
289 // Get tags for this identifier
290 $tags = $this->findTagsByIdentifier($entryIdentifier);
291 // Deassociate tags with this identifier
292 foreach ($tags as $tag) {
293 $identifiers = $this->findIdentifiersByTag($tag);
294 // Formally array_search() below should never return FALSE due to
295 // the behavior of findTagsByIdentifier(). But if reverse index is
296 // corrupted, we still can get 'FALSE' from array_search(). This is
297 // not a problem because we are removing this identifier from
298 // anywhere.
299 if (($key = array_search($entryIdentifier, $identifiers)) !== FALSE) {
300 unset($identifiers[$key]);
301 if (count($identifiers)) {
302 apc_store($this->getIdentifierPrefix() . 'tag_' . $tag, $identifiers);
303 } else {
304 apc_delete($this->getIdentifierPrefix() . 'tag_' . $tag);
305 }
306 }
307 }
308 // Clear reverse tag index for this identifier
309 apc_delete($this->getIdentifierPrefix() . 'ident_' . $entryIdentifier);
310 }
311
312 /**
313 * Does nothing, as APC does GC itself
314 *
315 * @return void
316 */
317 public function collectGarbage() {
318
319 }
320
321 }