Added feature 9097: Introduce a new caching framework (backported from FLOW3)
[Packages/TYPO3.CMS.git] / t3lib / cache / backend / class.t3lib_cache_backend_memcached.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2008 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 Memcached
28 *
29 * This file is a backport from FLOW3
30 *
31 * @package TYPO3
32 * @subpackage t3lib_cache
33 * @version $Id$
34 */
35 class t3lib_cache_backend_Memcached extends t3lib_cache_AbstractBackend {
36
37 /**
38 * @var Memcache
39 */
40 protected $memcache;
41
42 /**
43 * @var array
44 */
45 protected $servers = array();
46
47 /**
48 * @var boolean whether the memcache uses compression or not (requires zlib)
49 */
50 protected $useCompressed;
51
52 /**
53 * @var string A prefix to seperate stored data from other data possible stored in the memcache
54 */
55 protected $identifierPrefix;
56
57 /**
58 * Constructs this backend
59 *
60 * @param string $context FLOW3's application context
61 * @param mixed $options Configuration options - depends on the actual backend
62 * @author Robert Lemke <robert@typo3.org>
63 */
64 public function __construct($options = array()) {
65 if (!extension_loaded('memcache')) {
66 throw new t3lib_cache_Exception(
67 'The PHP extension "memcached" must be installed and loaded in order to use the Memcached backend.',
68 1213987706
69 );
70 }
71
72 parent::__construct($options);
73
74 $this->memcache = new Memcache();
75 $this->identifierPrefix = 'TYPO3_' . md5(
76 t3lib_div::getIndpEnv('SCRIPT_FILENAME')
77 . php_sapi_name()
78 ) . '_';
79
80 if (!count($this->servers)) {
81 throw new t3lib_cache_Exception(
82 'No servers were given to Memcache',
83 1213115903
84 );
85 }
86
87 foreach ($this->servers as $serverConf) {
88 $conf = explode(':',$serverConf, 2);
89 $this->memcache->addServer($conf[0], $conf[1]);
90 }
91 }
92
93 /**
94 * setter for servers property
95 * should be an array of entries like host:port
96 *
97 * @param array An array of servers to add
98 * @return void
99 * @author Christian Jul Jensen <julle@typo3.org>
100 */
101 protected function setServers(array $servers) {
102 $this->servers = $servers;
103 }
104
105 /**
106 * Setter for useCompressed
107 *
108 * @param boolean $enableCompression
109 * @return void
110 * @author Christian Jul Jensen <julle@typo3.org>
111 */
112 protected function setCompression($enableCompression) {
113 $this->useCompressed = $enableCompression;
114 }
115
116 /**
117 * Saves data in the cache.
118 *
119 * @param string An identifier for this specific cache entry
120 * @param string The data to be stored
121 * @param array Tags to associate with this cache entry
122 * @param integer Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited liftime.
123 * @return void
124 * @throws t3lib_cache_Exception if no cache frontend has been set.
125 * @throws InvalidArgumentException if the identifier is not valid
126 * @throws t3lib_cache_exception_InvalidData if $data is not a string
127 * @author Christian Jul Jensen <julle@typo3.org>
128 * @author Karsten Dambekalns <karsten@typo3.org>
129 **/
130 public function save($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
131 if (!self::isValidEntryIdentifier($entryIdentifier)) {
132 throw new InvalidArgumentException(
133 '"' . $entryIdentifier . '" is not a valid cache entry identifier.',
134 1207149191
135 );
136 }
137
138 if (!$this->cache instanceof t3lib_cache_AbstractCache) {
139 throw new t3lib_cache_Exception(
140 'No cache frontend has been set yet via setCache().',
141 1207149215
142 );
143 }
144
145 if (!is_string($data)) {
146 throw new t3lib_cache_Exception_InvalidData(
147 'The specified data is of type "' . gettype($data) . '" but a string is expected.',
148 1207149231
149 );
150 }
151
152 foreach($tags as $tag) {
153 if (!self::isValidTag($tag)) {
154 throw new InvalidArgumentException(
155 '"' . $tag . '" is not a valid tag.',
156 1213120275
157 );
158 }
159 }
160
161 $expiration = $lifetime ? $lifetime : $this->defaultLifetime;
162 try {
163 $this->remove($entryIdentifier);
164 $success = $this->memcache->set(
165 $this->identifierPrefix . $entryIdentifier,
166 $data,
167 $this->useCompressed,
168 $expiration
169 );
170
171 if (!$success) {
172 throw new t3lib_cache_Exception(
173 'Memcache was unable to connect to any server.',
174 1207165277
175 );
176 }
177
178 $this->addTagsToTagIndex($tags);
179 $this->addIdentifierToTags($entryIdentifier, $tags);
180 } catch(Exception $exception) {
181 throw new t3lib_cache_Exception(
182 'Memcache was unable to connect to any server. ' . $exception->getMessage(),
183 1207208100
184 );
185 }
186 }
187
188 /**
189 * Loads data from the cache.
190 *
191 * @param string An identifier which describes the cache entry to load
192 * @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
193 * @author Christian Jul Jensen <julle@typo3.org>
194 * @author Karsten Dambekalns <karsten@typo3.org>
195 */
196 public function load($entryIdentifier) {
197 return $this->memcache->get($this->identifierPrefix . $entryIdentifier);
198 }
199
200 /**
201 * Checks if a cache entry with the specified identifier exists.
202 *
203 * @param string An identifier specifying the cache entry
204 * @return boolean TRUE if such an entry exists, FALSE if not
205 * @author Christian Jul Jensen <julle@typo3.org>
206 * @author Karsten Dambekalns <karsten@typo3.org>
207 */
208 public function has($entryIdentifier) {
209 return (boolean) $this->memcache->get($this->identifierPrefix . $entryIdentifier);
210 }
211
212 /**
213 * Removes all cache entries matching the specified identifier.
214 * Usually this only affects one entry but if - for what reason ever -
215 * old entries for the identifier still exist, they are removed as well.
216 *
217 * @param string Specifies the cache entry to remove
218 * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
219 * @author Christian Jul Jensen <julle@typo3.org>
220 * @author Karsten Dambekalns <karsten@typo3.org>
221 */
222 public function remove($entryIdentifier) {
223 $this->removeIdentifierFromAllTags($entryIdentifier);
224 return $this->memcache->delete($this->identifierPrefix . $entryIdentifier);
225 }
226
227 /**
228 * Finds and returns all cache entries which are tagged by the specified tag.
229 * The asterisk ("*") is allowed as a wildcard at the beginning and the end of
230 * the tag.
231 *
232 * @param string The tag to search for, the "*" wildcard is supported
233 * @return array An array of entries with all matching entries. An empty array if no entries matched
234 * @author Karsten Dambekalns <karsten@typo3.org>
235 */
236 public function findEntriesByTag($tag) {
237 if (!self::isValidTag($tag)) {
238 throw new InvalidArgumentException(
239 '"' . $tag . '" is not a valid tag.',
240 1213120307
241 );
242 }
243
244 $entries = array();
245 $identifiers = $this->findIdentifiersTaggedWith($tag);
246 foreach($identifiers as $identifier) {
247 $entries[] = $this->load($identifier);
248 }
249
250 return $entries;
251 }
252
253 /**
254 * Removes all cache entries of this cache.
255 *
256 * Beware that this flushes the complete memcached, not only the cache
257 * entries we stored there. We do this because:
258 * it is expensive to keep track of all identifiers we put there
259 * memcache is a cache, you should never rely on things being there
260 *
261 * @return void
262 * @author Karsten Dambekalns <karsten@typo3.org>
263 */
264 public function flush() {
265 $this->memcache->flush();
266 }
267
268 /**
269 * Removes all cache entries of this cache which are tagged by the specified tag.
270 *
271 * @param string $tag The tag the entries must have
272 * @return void
273 * @author Karsten Dambekalns <karsten@typo3.org>
274 */
275 public function flushByTag($tag) {
276 $identifiers = $this->findIdentifiersTaggedWith($tag);
277 foreach($identifiers as $identifier) {
278 $this->remove($identifier);
279 }
280 }
281
282 /**
283 * Returns an array with all known tags
284 *
285 * @return array
286 * @author Karsten Dambekalns <karsten@typo3.org>
287 */
288 protected function getTagIndex() {
289 return (array) $this->memcache->get($this->identifierPrefix . '_tagIndex');
290 }
291
292 /**
293 * Saves the tags known to the backend
294 *
295 * @param array Array of tags
296 * @author Karsten Dambekalns <karsten@typo3.org>
297 */
298 protected function setTagIndex(array $tags) {
299 $this->memcache->set(
300 $this->identifierPrefix . '_tagIndex',
301 array_unique($tags),
302 0,
303 0
304 );
305 }
306
307 /**
308 * Adds the given tags to the tag index
309 *
310 * @param array Array of tags
311 * @return void
312 * @author Karsten Dambekalns <karsten@typo3.org>
313 */
314 protected function addTagsToTagIndex(array $tags) {
315 if(count($tags)) {
316 $this->setTagIndex(array_merge($tags, $this->getTagIndex()));
317 }
318 }
319
320 /**
321 * Removes the given tags from the tag index
322 *
323 * @param array $tags
324 * @return void
325 * @author Karsten Dambekalns <karsten@typo3.org>
326 */
327 protected function removeTagsFromTagIndex(array $tags) {
328 if(count($tags)) {
329 $this->setTagIndex(array_diff($this->getTagIndex(), $tags));
330 }
331 }
332
333 /**
334 * Associates the identifier with the given tags
335 *
336 * @param string $entryIdentifier
337 * @param array Array of tags
338 * @author Karsten Dambekalns <karsten@typo3.org>
339 */
340 protected function addIdentifierToTags($entryIdentifier, array $tags) {
341 foreach($tags as $tag) {
342 $identifiers = $this->findIdentifiersTaggedWith($tag);
343 $identifiers[] = $entryIdentifier;
344 $this->memcache->set(
345 $this->identifierPrefix . '_tag_' . $tag,
346 array_unique($identifiers)
347 );
348 }
349 }
350
351 /**
352 * Removes association of the identifier with the given tags
353 *
354 * @param string $entryIdentifier
355 * @param array Array of tags
356 * @author Karsten Dambekalns <karsten@typo3.org>
357 */
358 protected function removeIdentifierFromAllTags($entryIdentifier) {
359 $tags = $this->getTagIndex();
360
361 foreach($tags as $tag) {
362 $identifiers = $this->findIdentifiersTaggedWith($tag);
363
364 if(array_search($entryIdentifier, $identifiers) !== FALSE) {
365 unset($identifiers[array_search($entryIdentifier, $identifiers)]);
366 }
367
368 if(count($identifiers)) {
369 $this->memcache->set(
370 $this->identifierPrefix . '_tag_' . $tag,
371 array_unique($identifiers)
372 );
373 } else {
374 $this->removeTagsFromTagIndex(array($tag));
375 $this->memcache->delete($this->identifierPrefix . '_tag_' . $tag);
376 }
377 }
378 }
379
380 /**
381 * Returns all identifiers associated with $tag
382 *
383 * @param string $tag
384 * @return array
385 * @author Karsten Dambekalns <karsten@typo3.org>
386 */
387 public function findIdentifiersTaggedWith($tag) {
388 $identifiers = $this->memcache->get($this->identifierPrefix . '_tag_' . $tag);
389
390 if($identifiers !== FALSE) {
391 return (array) $identifiers;
392 } else {
393 return array();
394 }
395 }
396 }
397
398
399 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_memcached.php']) {
400 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_memcached.php']);
401 }
402
403 ?>