[TASK] Re-work/simplify copyright header in PHP files - Part 2
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Cache / Backend / XcacheBackend.php
1 <?php
2 namespace TYPO3\CMS\Core\Cache\Backend;
3 use TYPO3\CMS\Core\Cache\Exception;
4
5 /**
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 /**
19 * A caching backend which stores cache entries by using xcache opcode cache.
20 *
21 * This backend uses the following types of keys:
22 * - tag_xxx
23 * xxx is tag name, value is array of associated identifiers identifier. This
24 * is "forward" tag index. It is mainly used for obtaining content by tag
25 * (get identifier by tag -> get content by identifier)
26 * - ident_xxx
27 * xxx is identifier, value is array of associated tags. This is "reverse" tag
28 * index. It provides quick access for all tags associated with this identifier
29 * and used when removing the identifier
30 *
31 * Each key is prepended with a prefix. By default prefix consists from two parts
32 * separated by underscore character and ends in yet another underscore character:
33 * - "TYPO3"
34 * - MD5 of script path and filename and SAPI name
35 * This prefix makes sure that keys from the different installations do not
36 * conflict.
37 *
38 * @author Philipp Gampe <philipp.gampe@typo3.org>
39 */
40 class XcacheBackend extends AbstractBackend implements TaggableBackendInterface {
41
42 /**
43 * A prefix to separate stored data from other data possible stored in the xcache
44 *
45 * @var string
46 */
47 protected $identifierPrefix;
48
49 /**
50 * Constructs this backend
51 *
52 * @param string $context FLOW3's application context
53 * @param array $options Configuration options
54 * @throws \TYPO3\CMS\Core\Cache\Exception If xcache PHP extension is not loaded
55 */
56 public function __construct($context, array $options = array()) {
57 if (!extension_loaded('xcache')) {
58 throw new Exception(
59 'The PHP extension "xcache" must be installed and loaded in order to use the xcache backend.',
60 1363116592
61 );
62 }
63 parent::__construct($context, $options);
64 }
65
66 /**
67 * Saves data in the cache
68 *
69 * @param string $entryIdentifier An identifier for this specific cache entry
70 * @param string $data The data to be stored
71 * @param array $tags Tags to associate with this cache entry
72 * @param integer $lifetime Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited liftime.
73 * @return void
74 * @throws \TYPO3\CMS\Core\Cache\Exception if no cache frontend has been set
75 * @throws \TYPO3\CMS\Core\Cache\Exception\InvalidDataException if $data is not a string
76 */
77 public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
78 if ($this->runningFromCliOrWrongConfiguration()) {
79 return;
80 }
81 if (!$this->cache instanceof \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface) {
82 throw new Exception(
83 'No cache frontend has been set yet via setCache().',
84 1363117491
85 );
86 }
87 if (!is_string($data)) {
88 throw new Exception\InvalidDataException(
89 'The specified data is of type "' . gettype($data) . '" but a string is expected.',
90 1363117435
91 );
92 }
93 $tags[] = '%XCBE%' . $this->cache->getIdentifier();
94 $expiration = $lifetime !== NULL ? $lifetime : $this->defaultLifetime;
95 $success = xcache_set($this->identifierPrefix . $entryIdentifier, $data, $expiration);
96 if ($success === TRUE) {
97 $this->removeIdentifierFromAllTags($entryIdentifier);
98 $this->addIdentifierToTags($entryIdentifier, $tags);
99 } else {
100 throw new Exception(
101 'Could not set value.',
102 1363117507
103 );
104 }
105 }
106
107 /**
108 * Loads data from the cache
109 *
110 * @param string $entryIdentifier An identifier which describes the cache entry to load
111 * @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
112 */
113 public function get($entryIdentifier) {
114 if ($this->runningFromCliOrWrongConfiguration()) {
115 return FALSE;
116 }
117 $value = xcache_get($this->identifierPrefix . $entryIdentifier);
118 return $value ?: FALSE;
119 }
120
121 /**
122 * Checks if a cache entry with the specified identifier exists
123 *
124 * @param string $entryIdentifier An identifier specifying the cache entry
125 * @return boolean TRUE if such an entry exists, FALSE if not
126 */
127 public function has($entryIdentifier) {
128 if ($this->runningFromCliOrWrongConfiguration()) {
129 return FALSE;
130 }
131 return xcache_isset($this->identifierPrefix . $entryIdentifier);
132 }
133
134 /**
135 * Removes all cache entries matching the specified identifier.
136 * Usually this only affects one entry but if - for what reason ever -
137 * old entries for the identifier still exist, they are removed as well.
138 *
139 * @param string $entryIdentifier Specifies the cache entry to remove
140 * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
141 */
142 public function remove($entryIdentifier) {
143 if ($this->runningFromCliOrWrongConfiguration()) {
144 return FALSE;
145 }
146 $this->removeIdentifierFromAllTags($entryIdentifier);
147 return xcache_unset($this->identifierPrefix . $entryIdentifier);
148 }
149
150 /**
151 * Finds and returns all cache entry identifiers which are tagged by the
152 * specified tag.
153 *
154 * @param string $tag The tag to search for
155 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
156 */
157 public function findIdentifiersByTag($tag) {
158 if ($this->runningFromCliOrWrongConfiguration()) {
159 return array();
160 }
161 $identifiers = xcache_get($this->identifierPrefix . 'tag_' . $tag);
162 return $identifiers ?: array();
163 }
164
165 /**
166 * Finds all tags for the given identifier. This function uses reverse tag
167 * index to search for tags.
168 *
169 * @param string $identifier Identifier to find tags by
170 * @return array Array with tags
171 */
172 protected function findTagsByIdentifier($identifier) {
173 if ($this->runningFromCliOrWrongConfiguration()) {
174 return array();
175 }
176 $tags = xcache_get($this->identifierPrefix . 'ident_' . $identifier);
177 return $tags ?: array();
178 }
179
180 /**
181 * Removes all cache entries of this cache
182 *
183 * @return void
184 * @throws \TYPO3\CMS\Core\Cache\Exception
185 */
186 public function flush() {
187 if ($this->runningFromCliOrWrongConfiguration()) {
188 return;
189 }
190 if (!$this->cache instanceof \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface) {
191 throw new Exception(
192 'Yet no cache frontend has been set via setCache().',
193 1363117531
194 );
195 }
196 $this->flushByTag('%XCBE%' . $this->cache->getIdentifier());
197 }
198
199 /**
200 * Removes all cache entries of this cache which are tagged by the specified
201 * tag.
202 *
203 * @param string $tag The tag the entries must have
204 * @return void
205 */
206 public function flushByTag($tag) {
207 $identifiers = $this->findIdentifiersByTag($tag);
208 foreach ($identifiers as $identifier) {
209 $this->remove($identifier);
210 }
211 }
212
213 /**
214 * Associates the identifier with the given tags
215 *
216 * @param string $entryIdentifier
217 * @param array $tags
218 * @return void
219 */
220 protected function addIdentifierToTags($entryIdentifier, array $tags) {
221 if ($this->runningFromCliOrWrongConfiguration()) {
222 return;
223 }
224 foreach ($tags as $tag) {
225 // Update tag-to-identifier index
226 $identifiers = $this->findIdentifiersByTag($tag);
227 if (array_search($entryIdentifier, $identifiers) === FALSE) {
228 $identifiers[] = $entryIdentifier;
229 xcache_set($this->identifierPrefix . 'tag_' . $tag, $identifiers);
230 }
231 // Update identifier-to-tag index
232 $existingTags = $this->findTagsByIdentifier($entryIdentifier);
233 if (array_search($entryIdentifier, $existingTags) === FALSE) {
234 xcache_set($this->identifierPrefix . 'ident_' . $entryIdentifier, array_merge($existingTags, $tags));
235 }
236 }
237 }
238
239 /**
240 * Removes association of the identifier with the given tags
241 *
242 * @param string $entryIdentifier
243 * @return void
244 */
245 protected function removeIdentifierFromAllTags($entryIdentifier) {
246 if ($this->runningFromCliOrWrongConfiguration()) {
247 return;
248 }
249 // Get tags for this identifier
250 $tags = $this->findTagsByIdentifier($entryIdentifier);
251 // Disassociate tags with this identifier
252 foreach ($tags as $tag) {
253 $identifiers = $this->findIdentifiersByTag($tag);
254 // Formally array_search() below should never return false due to
255 // the behavior of findTagsByIdentifier(). But if reverse index is
256 // corrupted, we still can get 'false' from array_search(). This is
257 // not a problem because we are removing this identifier from
258 // anywhere.
259 if (($key = array_search($entryIdentifier, $identifiers)) !== FALSE) {
260 unset($identifiers[$key]);
261 if (count($identifiers)) {
262 xcache_set($this->identifierPrefix . 'tag_' . $tag, $identifiers);
263 } else {
264 xcache_unset($this->identifierPrefix . 'tag_' . $tag);
265 }
266 }
267 }
268 // Clear reverse tag index for this identifier
269 xcache_unset($this->identifierPrefix . 'ident_' . $entryIdentifier);
270 }
271
272 /**
273 * Does nothing, as xcache does GC itself
274 *
275 * @return void
276 */
277 public function collectGarbage() {
278 }
279
280 /**
281 * Checks if backend is called from CLI context.
282 * In this case all methods fail silently as xcache user cache is not available in CLI context.
283 * xcache.var_size cat be zero or empty if in CLI mode, or if not correctly configured.
284 *
285 * @return boolean TRUE if misconfigured or in CLI mode
286 */
287 protected function runningFromCliOrWrongConfiguration() {
288 $varSize = ini_get('xcache.var_size');
289 return php_sapi_name() === 'cli' || empty($varSize);
290 }
291 }