Feature #6426 ยป class.t3lib_cache_backend_xcachebackend.php

Nikolas Hagelstein, 2010-02-10 09:15

 
1
<?php
2
class t3lib_cache_backend_XcacheBackend extends t3lib_cache_backend_AbstractBackend {
3

    
4
	/**
5
	 * A prefix to seperate stored data from other data possible stored in the XCache
6
	 *
7
	 * @var string
8
	 */
9
	protected $identifierPrefix;
10

    
11
	/**
12
	 * Constructs this backend
13
	 *
14
	 * @param mixed $options Configuration options - unused here
15
	 * @author Robert Lemke <robert@typo3.org>
16
	 * @author Karsten Dambekalns <karsten@typo3.org>
17
	 * @author Nikolas Hagelstein <nikolas.hagelstein@gmail.com>
18
	 */
19
	public function __construct($options = array()) {
20
		if (!extension_loaded('xcache')) {
21
			throw new t3lib_cache_Exception(
22
				'The PHP extension "xcache" must be installed and loaded in order to use the XCache backend.',
23
				1232985414
24
			);
25
		}
26

    
27
		parent::__construct($options);
28
	}
29

    
30
	/**
31
	 * Saves data in the cache.
32
	 *
33
	 * @param string $entryIdentifier An identifier for this specific cache entry
34
	 * @param string $data The data to be stored
35
	 * @param array $tags Tags to associate with this cache entry
36
	 * @param integer $lifetime Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited liftime.
37
	 * @return void
38
	 * @throws t3lib_cache_Exception if no cache frontend has been set.
39
	 * @throws InvalidArgumentException if the identifier is not valid
40
	 * @throws t3lib_cache_exception_InvalidData if $data is not a string
41
	 * @author Christian Jul Jensen <julle@typo3.org>
42
	 * @author Karsten Dambekalns <karsten@typo3.org>
43
	 * @author Nikolas Hagelstein <nikolas.hagelstein@gmail.com>
44
	 */
45
	public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
46
		if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
47
			throw new t3lib_cache_Exception(
48
				'No cache frontend has been set yet via setCache().',
49
				1232986818
50
			);
51
		}
52

    
53
		if (!is_string($data)) {
54
			throw new t3lib_cache_exception_InvalidData(
55
				'The specified data is of type "' . gettype($data) . '" but a string is expected.',
56
				1232986825
57
			);
58
		}
59

    
60
		$tags[] = '%XCACHEBE%' . $this->cache->getIdentifier();
61
		$expiration = $lifetime !== NULL ? $lifetime : $this->defaultLifetime;
62

    
63
		$success = xcache_set($this->identifierPrefix . $entryIdentifier, $data, $expiration);
64
		if ($success === TRUE) {
65
			$this->removeIdentifierFromAllTags($entryIdentifier);
66
			$this->addTagsToTagIndex($tags);
67
			$this->addIdentifierToTags($entryIdentifier, $tags);
68
		} else {
69
			throw new t3lib_cache_Exception(
70
				'Could not set value.',
71
				1232986877
72
			);
73
		}
74
	}
75

    
76
	/**
77
	 * Loads data from the cache.
78
	 *
79
	 * @param string $entryIdentifier An identifier which describes the cache entry to load
80
	 * @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
81
	 * @author Nikolas Hagelstein <nikolas.hagelstein@gmail.com>
82
	 */
83
	public function get($entryIdentifier) {
84
		$value = xcache_get($this->identifierPrefix . $entryIdentifier);
85
		return $value;
86
	}
87

    
88
	/**
89
	 * Checks if a cache entry with the specified identifier exists.
90
	 *
91
	 * @param string $entryIdentifier An identifier specifying the cache entry
92
	 * @return boolean TRUE if such an entry exists, FALSE if not
93
	 * @author Nikolas Hagelstein <nikolas.hagelstein@gmail.com>
94
	 */
95
	public function has($entryIdentifier) {
96
		$success = xcache_isset($this->identifierPrefix . $entryIdentifier);
97
		return $success;
98
	}
99

    
100
	/**
101
	 * Removes all cache entries matching the specified identifier.
102
	 * Usually this only affects one entry but if - for what reason ever -
103
	 * old entries for the identifier still exist, they are removed as well.
104
	 *
105
	 * @param string $entryIdentifier Specifies the cache entry to remove
106
	 * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
107
	 * @author Christian Jul Jensen <julle@typo3.org>
108
	 * @author Karsten Dambekalns <karsten@typo3.org>
109
	 * @author Nikolas Hagelstein <nikolas.hagelstein@gmail.com>
110
	 */
111
	public function remove($entryIdentifier) {
112
		$this->removeIdentifierFromAllTags($entryIdentifier);
113

    
114
		return xcache_unset($this->identifierPrefix . $entryIdentifier);
115
	}
116

    
117
	/**
118
	 * Finds and returns all cache entry identifiers which are tagged by the
119
	 * specified tag.
120
	 *
121
	 * @param string $tag The tag to search for
122
	 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
123
	 * @author Karsten Dambekalns <karsten@typo3.org>
124
	 * @author Nikolas Hagelstein <nikolas.hagelstein@gmail.com>
125
	 */
126
	public function findIdentifiersByTag($tag) {
127
		$identifiers = xcache_get($this->identifierPrefix . 'tag_' . $tag);
128

    
129
		if ($identifiers === FALSE) {
130
			return array();
131
		} else {
132
			return (array) $identifiers;
133
		}
134
	}
135

    
136
	/**
137
	 * Finds and returns all cache entry identifiers which are tagged by the
138
	 * specified tags.
139
	 *
140
	 * @param array Array of tags to search for
141
	 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
142
	 * @author Ingo Renner <ingo@typo3.org>
143
	 */
144
	public function findIdentifiersByTags(array $tags) {
145
		$taggedEntries = array();
146
		$foundEntries  = array();
147

    
148
		foreach ($tags as $tag) {
149
			$taggedEntries[$tag] = $this->findIdentifiersByTag($tag);
150
		}
151

    
152
		$intersectedTaggedEntries = call_user_func_array('array_intersect', $taggedEntries);
153

    
154
		foreach ($intersectedTaggedEntries as $entryIdentifier) {
155
			if ($this->has($entryIdentifier)) {
156
				$foundEntries[$entryIdentifier] = $entryIdentifier;
157
			}
158
		}
159

    
160
		return $foundEntries;
161
	}
162

    
163
	/**
164
	 * Finds all tags for the given identifier. This function uses reverse tag
165
	 * index to search for tags.
166
	 *
167
	 * @param string $identifier Identifier to find tags by
168
	 * @return array Array with tags
169
	 * @author Karsten Dambekalns <karsten@typo3.org>
170
	 * @author Nikolas Hagelstein <nikolas.hagelstein@gmail.com>
171
	 *
172
	 */
173
	protected function findTagsByIdentifier($identifier) {
174
		$tags = xcache_get($this->identifierPrefix . 'ident_' . $identifier);
175
		return ($tags ? (array)$tags : array());
176
	}
177

    
178
	/**
179
	 * Removes all cache entries of this cache.
180
	 *
181
	 * @return void
182
	 * @author Karsten Dambekalns <karsten@typo3.org>
183
	 * @author Nikolas Hagelstein <nikolas.hagelstein@gmail.com>
184
	 */
185
	public function flush() {
186
		if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
187
			throw new t3lib_cache_Exception(
188
				'Yet no cache frontend has been set via setCache().',
189
				1232986971
190
			);
191
		}
192

    
193
		$this->flushByTag('%XCACHEBE%' . $this->cache->getIdentifier());
194
	}
195

    
196
	/**
197
	 * Removes all cache entries of this cache which are tagged by the specified
198
	 * tag.
199
	 *
200
	 * @param string $tag The tag the entries must have
201
	 * @return void
202
	 * @author Karsten Dambekalns <karsten@typo3.org>
203
	 */
204
	public function flushByTag($tag) {
205
		$identifiers = $this->findIdentifiersByTag($tag);
206

    
207
		foreach ($identifiers as $identifier) {
208
			$this->remove($identifier);
209
		}
210
	}
211

    
212
	/**
213
	 * Removes all cache entries of this cache which are tagged by the specified tag.
214
	 *
215
	 * @param array	The tags the entries must have
216
	 * @return void
217
	 * @author Ingo Renner <ingo@typo3.org>
218
	 */
219
	public function flushByTags(array $tags) {
220
		foreach ($tags as $tag) {
221
			$this->flushByTag($tag);
222
		}
223
	}
224

    
225
	/**
226
	 * Returns an array with all known tags
227
	 *
228
	 * @return array
229
	 * @author Karsten Dambekalns <karsten@typo3.org>
230
	 * @author Nikolas Hagelstein <nikolas.hagelstein@gmail.com>
231
	 */
232
	protected function getTagIndex() {
233
		$tagIndex = xcache_get($this->identifierPrefix . 'tagIndex');
234
		return ($tagIndex ? (array)$tagIndex : array());
235
	}
236

    
237
	/**
238
	 * Saves the tags known to the backend
239
	 *
240
	 * @param array $tags
241
	 * @author Karsten Dambekalns <karsten@typo3.org>
242
	 * @author Nikolas Hagelstein <nikolas.hagelstein@gmail.com>
243
	 */
244
	protected function setTagIndex(array $tags) {
245
		xcache_set($this->identifierPrefix . 'tagIndex', array_unique($tags));
246
	}
247

    
248
	/**
249
	 * Adds the given tags to the tag index
250
	 *
251
	 * @param array $tags
252
	 * @return void
253
	 * @author Karsten Dambekalns <karsten@typo3.org>
254
	 */
255
	protected function addTagsToTagIndex(array $tags) {
256
		if (count($tags)) {
257
			$this->setTagIndex(array_merge($tags, $this->getTagIndex()));
258
		}
259
	}
260

    
261
	/**
262
	 * Removes the given tags from the tag index
263
	 *
264
	 * @param array $tags
265
	 * @return void
266
	 * @author Karsten Dambekalns <karsten@typo3.org>
267
	 */
268
	protected function removeTagsFromTagIndex(array $tags) {
269
		if (count($tags)) {
270
			$this->setTagIndex(array_diff($this->getTagIndex(), $tags));
271
		}
272
	}
273

    
274
	/**
275
	 * Associates the identifier with the given tags
276
	 *
277
	 * @param string $entryIdentifier
278
	 * @param array $tags
279
	 * @author Karsten Dambekalns <karsten@typo3.org>
280
	 * @author Dmitry Dulepov <dmitry.@typo3.org>
281
	 * @author Nikolas Hagelstein <nikolas.hagelstein@gmail.com>
282
	 *
283
	 */
284
	protected function addIdentifierToTags($entryIdentifier, array $tags) {
285
		foreach ($tags as $tag) {
286
				// Update tag-to-identifier index
287
			$identifiers = $this->findIdentifiersByTag($tag);
288
			if (array_search($entryIdentifier, $identifiers) === FALSE) {
289
				$identifiers[] = $entryIdentifier;
290
				xcache_set($this->identifierPrefix . 'tag_' . $tag, $identifiers);
291
			}
292

    
293
				// Update identifier-to-tag index
294
			$existingTags = $this->findTagsByIdentifier($entryIdentifier);
295
			if (array_search($entryIdentifier, $existingTags) === false) {
296
				xcache_set($this->identifierPrefix . 'ident_' . $entryIdentifier, array_merge($existingTags, $tags));
297
			}
298

    
299
		}
300
	}
301

    
302
	/**
303
	 * Removes association of the identifier with the given tags
304
	 *
305
	 * @param string $entryIdentifier
306
	 * @param array $tags
307
	 * @author Karsten Dambekalns <karsten@typo3.org>
308
	 * @author Dmitry Dulepov <dmitry.@typo3.org>
309
	 * @author Nikolas Hagelstein <nikolas.hagelstein@gmail.com>
310
	 */
311
	protected function removeIdentifierFromAllTags($entryIdentifier) {
312
			// Get tags for this identifier
313
		$tags = $this->findTagsByIdentifier($entryIdentifier);
314
			// Deassociate tags with this identifier
315
		foreach ($tags as $tag) {
316
			$identifiers = $this->findIdentifiersByTag($tag);
317
				// Formally array_search() below should never return false due to
318
				// the behavior of findTagsByIdentifier(). But if reverse index is
319
				// corrupted, we still can get 'false' from array_search(). This is
320
				// not a problem because we are removing this identifier from
321
				// anywhere.
322
			if (($key = array_search($entryIdentifier, $identifiers)) !== FALSE) {
323
				unset($identifiers[$key]);
324
				if (count($identifiers)) {
325
					xcache_set($this->identifierPrefix . 'tag_' . $tag, $identifiers);
326
				} else {
327
					$this->removeTagsFromTagIndex(array($tag));
328
					xcache_unset($this->identifierPrefix . 'tag_' . $tag);
329
				}
330
			}
331
		}
332
			// Clear reverse tag index for this identifier
333
		xcache_unset($this->identifierPrefix . 'ident_' . $entryIdentifier);
334
	}
335

    
336
	/**
337
	 * Does nothing, as XCache does GC itself
338
	 *
339
	 * @return void
340
	 */
341
	public function collectGarbage() {
342

    
343
	}
344

    
345
}
346

    
347
?>
    (1-1/1)