9fcf1cd7aa6e876420575d8b28d075ae7cf56404
[Packages/TYPO3.CMS.git] / t3lib / cache / backend / class.t3lib_cache_backend_dbbackend.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2008-2009 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 in database tables
28 *
29 * @package TYPO3
30 * @subpackage t3lib_cache
31 * @version $Id$
32 */
33 class t3lib_cache_backend_DbBackend extends t3lib_cache_backend_AbstractBackend {
34
35 protected $cacheTable;
36 protected $tagsTable;
37
38 protected $identifierField;
39 protected $creationField;
40 protected $lifetimeField;
41 protected $notExpiredStatement;
42 protected $tableList;
43 protected $tableJoin;
44
45 /**
46 * Constructs this backend
47 *
48 * @param mixed Configuration options - depends on the actual backend
49 */
50 public function __construct(array $options = array()) {
51 parent::__construct($options);
52
53 if (!$this->cacheTable) {
54 throw new t3lib_cache_Exception(
55 'No table to write data to has been set using the setting "cacheTable".',
56 1253534136
57 );
58 }
59
60 if (!$this->tagsTable) {
61 throw new t3lib_cache_Exception(
62 'No table to write tags to has been set using the setting "tagsTable".',
63 1253534137
64 );
65 }
66
67 $this->initializeCommonReferences();
68 }
69
70 /**
71 * Initializes common references used in this backend.
72 *
73 * @return void
74 */
75 protected function initializeCommonReferences() {
76 $this->identifierField = $this->cacheTable . '.identifier';
77 $this->creationField = $this->cacheTable . '.crdate';
78 $this->lifetimeField = $this->cacheTable . '.lifetime';
79 $this->tableList = $this->cacheTable . ', ' . $this->tagsTable;
80 $this->tableJoin = $this->identifierField . ' = ' . $this->tagsTable . '.identifier';
81 $this->notExpiredStatement = '(' . $this->creationField . ' + ' . $this->lifetimeField .
82 ' >= ' . $GLOBALS['EXEC_TIME'] . ' OR ' . $this->lifetimeField . ' = 0)';
83 }
84
85 /**
86 * Saves data in a cache file.
87 *
88 * @param string An identifier for this specific cache entry
89 * @param string The data to be stored
90 * @param array Tags to associate with this cache entry
91 * @param integer Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited liftime.
92 * @return void
93 * @throws t3lib_cache_Exception if no cache frontend has been set.
94 * @throws t3lib_cache_exception_InvalidData if the data to be stored is not a string.
95 * @author Ingo Renner <ingo@typo3.org>
96 */
97 public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
98 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
99 throw new t3lib_cache_Exception(
100 'No cache frontend has been set via setCache() yet.',
101 1236518288
102 );
103 }
104
105 if (!is_string($data)) {
106 throw new t3lib_cache_exception_InvalidData(
107 'The specified data is of type "' . gettype($data) . '" but a string is expected.',
108 1236518298
109 );
110 }
111
112 if (is_null($lifetime)) {
113 $lifetime = $this->defaultLifetime;
114 }
115
116 $this->remove($entryIdentifier);
117
118 $GLOBALS['TYPO3_DB']->exec_INSERTquery(
119 $this->cacheTable,
120 array(
121 'identifier' => $entryIdentifier,
122 'crdate' => $GLOBALS['EXEC_TIME'],
123 'content' => $data,
124 'lifetime' => $lifetime
125 )
126 );
127
128 foreach ($tags as $tag) {
129 $GLOBALS['TYPO3_DB']->exec_INSERTquery(
130 $this->tagsTable,
131 array(
132 'identifier' => $entryIdentifier,
133 'tag' => $tag,
134 )
135 );
136 }
137 }
138
139 /**
140 * Loads data from a cache file.
141 *
142 * @param string An identifier which describes the cache entry to load
143 * @return mixed The cache entry's data as a string or FALSE if the cache entry could not be loaded
144 * @author Ingo Renner <ingo@typo3.org>
145 */
146 public function get($entryIdentifier) {
147 $cacheEntry = false;
148
149 $cacheEntries = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
150 'content',
151 $this->cacheTable,
152 'identifier = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($entryIdentifier, $this->cacheTable) . ' '
153 . 'AND (crdate + lifetime >= ' . $GLOBALS['EXEC_TIME'] . ' OR lifetime = 0)'
154 );
155
156 if (count($cacheEntries) == 1) {
157 $cacheEntry = $cacheEntries[0]['content'];
158 }
159
160 return $cacheEntry;
161 }
162
163 /**
164 * Checks if a cache entry with the specified identifier exists.
165 *
166 * @param unknown_type
167 * @return boolean TRUE if such an entry exists, FALSE if not
168 * @author Ingo Renner <ingo@typo3.org>
169 */
170 public function has($entryIdentifier) {
171 $hasEntry = false;
172
173 $cacheEntries = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
174 'content',
175 $this->cacheTable,
176 'identifier = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($entryIdentifier, $this->cacheTable) . ' '
177 . 'AND crdate + lifetime >= ' . $GLOBALS['EXEC_TIME']
178 );
179
180 if (count($cacheEntries) == 1) {
181 $hasEntry = true;
182 }
183
184 return $hasEntry;
185 }
186
187 /**
188 * Removes all cache entries matching the specified identifier.
189 * Usually this only affects one entry.
190 *
191 * @param string 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 * @author Ingo Renner <ingo@typo3.org>
194 */
195 public function remove($entryIdentifier) {
196 $entryRemoved = false;
197
198 $res = $GLOBALS['TYPO3_DB']->exec_DELETEquery(
199 $this->cacheTable,
200 'identifier = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($entryIdentifier, $this->cacheTable)
201 );
202
203 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
204 $this->tagsTable,
205 'identifier = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($entryIdentifier, $this->tagsTable)
206 );
207
208 if($GLOBALS['TYPO3_DB']->sql_affected_rows($res) == 1) {
209 $entryRemoved = true;
210 }
211
212 return $entryRemoved;
213 }
214
215 /**
216 * Finds and returns all cache entries which are tagged by the specified tag.
217 *
218 * @param string The tag to search for
219 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
220 * @author Ingo Renner <ingo@typo3.org>
221 */
222 public function findIdentifiersByTag($tag) {
223 $cacheEntryIdentifiers = array();
224
225 $cacheEntryIdentifierRows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
226 $this->identifierField,
227 $this->tableList,
228 $this->getQueryForTag($tag) .
229 ' AND ' . $this->tableJoin .
230 ' AND ' . $this->notExpiredStatement,
231 $this->identifierField
232 );
233
234 foreach ($cacheEntryIdentifierRows as $cacheEntryIdentifierRow) {
235 $cacheEntryIdentifiers[$cacheEntryIdentifierRow['identifier']] = $cacheEntryIdentifierRow['identifier'];
236 }
237
238 return $cacheEntryIdentifiers;
239 }
240
241 /**
242 * Finds and returns all cache entry identifiers which are tagged by the
243 * specified tags.
244 *
245 * @param array Array of tags to search for
246 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
247 * @author Ingo Renner <ingo@typo3.org>
248 */
249 public function findIdentifiersByTags(array $tags) {
250 $cacheEntryIdentifiers = array();
251 $whereClause = array();
252
253 foreach ($tags as $tag) {
254 $whereClause[] = $this->getQueryForTag($tag);
255 }
256
257 $whereClause[] = $this->tableJoin;
258 $whereClause[] = $this->notExpiredStatement;
259
260 $cacheEntryIdentifierRows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
261 $this->identifierField,
262 $this->tableList,
263 implode(' AND ', $whereClause),
264 $this->identifierField
265 );
266
267 foreach ($cacheEntryIdentifierRows as $cacheEntryIdentifierRow) {
268 $cacheEntryIdentifiers[$cacheEntryIdentifierRow['identifier']] = $cacheEntryIdentifierRow['identifier'];
269 }
270
271 return $cacheEntryIdentifiers;
272 }
273
274 /**
275 * Removes all cache entries of this cache.
276 *
277 * @return void
278 * @author Ingo Renner <ingo@typo3.org>
279 */
280 public function flush() {
281 $GLOBALS['TYPO3_DB']->sql_query('TRUNCATE ' . $this->cacheTable);
282 $GLOBALS['TYPO3_DB']->sql_query('TRUNCATE ' . $this->tagsTable);
283 }
284
285 /**
286 * Removes all cache entries of this cache which are tagged by the specified tag.
287 *
288 * @param string The tag the entries must have
289 * @return void
290 */
291 public function flushByTag($tag) {
292 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
293 $this->cacheTable,
294 'identifier IN (' .
295 $GLOBALS['TYPO3_DB']->SELECTquery(
296 'identifier',
297 $this->tagsTable,
298 $this->getQueryForTag($tag)
299 ) .
300 ')'
301 );
302
303 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
304 $this->tagsTable,
305 $this->getQueryForTag($tag)
306 );
307 }
308
309 /**
310 * Removes all cache entries of this cache which are tagged by the specified tags.
311 *
312 * @param array The tags the entries must have
313 * @return void
314 */
315 public function flushByTags(array $tags) {
316 if (count($tags)) {
317 $listQueryConditions = array();
318 foreach ($tags as $tag) {
319 $listQueryConditions[$tag] = $this->getQueryForTag($tag);
320 }
321
322 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
323 $this->cacheTable,
324 'identifier IN (' .
325 $GLOBALS['TYPO3_DB']->SELECTquery(
326 'identifier',
327 $this->tagsTable,
328 implode(' OR ', $listQueryConditions)
329 ) .
330 ')'
331 );
332
333 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
334 $this->tagsTable,
335 implode(' OR ', $listQueryConditions)
336 );
337 }
338 }
339
340 /**
341 * Does garbage collection
342 *
343 * @return void
344 * @author Ingo Renner <ingo@typo3.org>
345 */
346 public function collectGarbage() {
347 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
348 $this->tagsTable,
349 'identifier IN (' .
350 $GLOBALS['TYPO3_DB']->SELECTquery(
351 'identifier',
352 $this->cacheTable,
353 'crdate + lifetime < ' . $GLOBALS['EXEC_TIME'] . ' AND lifetime > 0'
354 ) .
355 ')'
356 );
357
358 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
359 $this->cacheTable,
360 'crdate + lifetime < ' . $GLOBALS['EXEC_TIME'] . ' AND lifetime > 0'
361 );
362 }
363
364 /**
365 * Sets the table where the cache entries are stored. The specified table
366 * must exist already.
367 *
368 * @param string The table.
369 * @return void
370 * @throws t3lib_cache_Exception if the table does not exist.
371 * @author Ingo Renner <ingo@typo3.org>
372 */
373 public function setCacheTable($cacheTable) {
374 /*
375
376 TODO reenable this check or remove it before 4.3 final
377
378 This check causes mysql warnings when not being logged in and calling
379 typo3/backend.php or the install tool.
380 Reason: the caches in typo3/init.php get initialized before a DB connection
381 has been established.
382 Related Question: Why aren't there warnings in the FE as the caches get
383 initialized in tslib_fe's constructor which is also before a DB conection
384 exsits?
385 Assumption Ingo Renner: Is a custom error_reporting level causing that?
386
387 There's also an unit test for that check (also deactivated for now).
388
389 $result = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
390 'id',
391 $cacheTable,
392 '',
393 '',
394 '',
395 1
396 );
397
398 if (!is_array($result)) {
399 throw new t3lib_cache_Exception(
400 'The table "' . $cacheTable . '" does not exist.',
401 1236516444
402 );
403 }
404 */
405 $this->cacheTable = $cacheTable;
406 $this->initializeCommonReferences();
407 }
408
409 /**
410 * Returns the table where the cache entries are stored.
411 *
412 * @return string The cache table.
413 * @author Ingo Renner <ingo@typo3.org>
414 */
415 public function getCacheTable() {
416 return $this->cacheTable;
417 }
418
419 /**
420 * Sets the table where cache tags are stored.
421 *
422 * @param string $tagsTabls: Name of the table
423 * @return void
424 */
425 public function setTagsTable($tagsTable) {
426 $this->tagsTable = $tagsTable;
427 $this->initializeCommonReferences();
428 }
429
430 /**
431 * Gets the table where cache tags are stored.
432 *
433 * @return string Name of the table storing tags
434 */
435 public function getTagsTable() {
436 return $this->tagsTable;
437 }
438
439 /**
440 * Gets the query to be used for selecting entries by a tag. The asterisk ("*")
441 * is allowed as a wildcard at the beginning and the end of a tag.
442 *
443 * @param string The tag to search for, the "*" wildcard is supported
444 * @return string the query to be used for selecting entries
445 * @author Oliver Hader <oliver@typo3.org>
446 */
447 protected function getQueryForTag($tag) {
448 if (strpos($tag, '*') === false) {
449 $query = $this->tagsTable . '.tag = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($tag, $this->tagsTable);
450 } else {
451 $patternForLike = $GLOBALS['TYPO3_DB']->escapeStrForLike(
452 $GLOBALS['TYPO3_DB']->quoteStr($tag, $this->tagsTable),
453 $this->tagsTable
454 );
455 $query = $this->tagsTable . '.tag LIKE \'' . $patternForLike . '\'';
456 }
457
458 return $query;
459 }
460 }
461
462
463 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_dbbackend.php']) {
464 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_dbbackend.php']);
465 }
466
467 ?>