d69ed063264aa67c0adc8776f2301644d5051cf3
[Packages/TYPO3.CMS.git] / t3lib / cache / backend / class.t3lib_cache_backend_dbbackend.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2008-2011 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 * A caching backend which stores cache entries in database tables
27 *
28 * @package TYPO3
29 * @subpackage t3lib_cache
30 * @api
31 * @scope prototype
32 */
33 class t3lib_cache_backend_DbBackend extends t3lib_cache_backend_AbstractBackend {
34
35 /**
36 * @var string Name of the cache data table
37 */
38 protected $cacheTable;
39
40 /**
41 * @var string Name of the cache tags table
42 */
43 protected $tagsTable;
44
45 /**
46 * @var boolean Indicates wether data is compressed or not (requires php zlib)
47 */
48 protected $compression = FALSE;
49
50 /**
51 * @var integer -1 to 9, indicates zlib compression level: -1 = default level 6, 0 = no compression, 9 maximum compression
52 */
53 protected $compressionLevel = -1;
54
55 protected $identifierField;
56 protected $creationField;
57 protected $lifetimeField;
58 protected $notExpiredStatement;
59 protected $tableList;
60 protected $tableJoin;
61
62 /**
63 * Set cache frontend instance and calculate data and tags table name
64 *
65 * @param t3lib_cache_frontend_Frontend $cache The frontend for this backend
66 * @return void
67 * @author Christian Kuhn <lolli@schwarzbu.ch>
68 * @api
69 */
70 public function setCache(t3lib_cache_frontend_Frontend $cache) {
71 parent::setCache($cache);
72
73 $this->cacheTable = 'cachingframework_' . $this->cacheIdentifier;
74 $this->tagsTable = 'cachingframework_' . $this->cacheIdentifier . '_tags';
75 $this->initializeCommonReferences();
76 }
77
78 /**
79 * Initializes common references used in this backend.
80 *
81 * @return void
82 */
83 protected function initializeCommonReferences() {
84 $this->identifierField = $this->cacheTable . '.identifier';
85 $this->creationField = $this->cacheTable . '.crdate';
86 $this->lifetimeField = $this->cacheTable . '.lifetime';
87 $this->tableList = $this->cacheTable . ', ' . $this->tagsTable;
88 $this->tableJoin = $this->identifierField . ' = ' . $this->tagsTable . '.identifier';
89 $this->notExpiredStatement = '(' . $this->creationField . ' + ' . $this->lifetimeField .
90 ' >= ' . $GLOBALS['EXEC_TIME'] . ' OR ' . $this->lifetimeField . ' = 0)';
91 }
92
93 /**
94 * Saves data in a cache file.
95 *
96 * @param string An identifier for this specific cache entry
97 * @param string The data to be stored
98 * @param array Tags to associate with this cache entry
99 * @param integer Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited liftime.
100 * @return void
101 * @throws t3lib_cache_Exception if no cache frontend has been set.
102 * @throws t3lib_cache_exception_InvalidData if the data to be stored is not a string.
103 * @author Ingo Renner <ingo@typo3.org>
104 */
105 public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
106 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
107 throw new t3lib_cache_Exception(
108 'No cache frontend has been set via setCache() yet.',
109 1236518288
110 );
111 }
112
113 if (!is_string($data)) {
114 throw new t3lib_cache_exception_InvalidData(
115 'The specified data is of type "' . gettype($data) . '" but a string is expected.',
116 1236518298
117 );
118 }
119
120 if (is_null($lifetime)) {
121 $lifetime = $this->defaultLifetime;
122 }
123
124 $this->remove($entryIdentifier);
125
126 if ($this->compression) {
127 $data = gzcompress($data, $this->compressionLevel);
128 }
129
130 $GLOBALS['TYPO3_DB']->exec_INSERTquery(
131 $this->cacheTable,
132 array(
133 'identifier' => $entryIdentifier,
134 'crdate' => $GLOBALS['EXEC_TIME'],
135 'content' => $data,
136 'lifetime' => $lifetime
137 )
138 );
139
140 if (count($tags)) {
141 $fields = array();
142 $fields[] = 'identifier';
143 $fields[] = 'tag';
144
145 $tagRows = array();
146 foreach ($tags as $tag) {
147 $tagRow = array();
148 $tagRow[] = $entryIdentifier;
149 $tagRow[] = $tag;
150 $tagRows[] = $tagRow;
151 }
152
153 $GLOBALS['TYPO3_DB']->exec_INSERTmultipleRows(
154 $this->tagsTable,
155 $fields,
156 $tagRows
157 );
158 }
159 }
160
161 /**
162 * Loads data from a cache file.
163 *
164 * @param string An identifier which describes the cache entry to load
165 * @return mixed The cache entry's data as a string or FALSE if the cache entry could not be loaded
166 * @author Ingo Renner <ingo@typo3.org>
167 */
168 public function get($entryIdentifier) {
169 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
170 throw new t3lib_cache_Exception(
171 'No cache frontend has been set via setCache() yet.',
172 1308435810
173 );
174 }
175
176 $cacheEntry = FALSE;
177
178 $cacheEntry = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
179 'content',
180 $this->cacheTable,
181 'identifier = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($entryIdentifier, $this->cacheTable) . ' '
182 . 'AND (crdate + lifetime >= ' . $GLOBALS['EXEC_TIME'] . ' OR lifetime = 0)'
183 );
184
185 if (is_array($cacheEntry)) {
186 $cacheEntry = $cacheEntry['content'];
187 }
188
189 if ($this->compression && strlen($cacheEntry)) {
190 $cacheEntry = gzuncompress($cacheEntry);
191 }
192
193 return $cacheEntry;
194 }
195
196 /**
197 * Checks if a cache entry with the specified identifier exists.
198 *
199 * @param string Specifies the identifier to check for existence
200 * @return boolean TRUE if such an entry exists, FALSE if not
201 * @author Ingo Renner <ingo@typo3.org>
202 */
203 public function has($entryIdentifier) {
204 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
205 throw new t3lib_cache_Exception(
206 'No cache frontend has been set via setCache() yet.',
207 1308435811
208 );
209 }
210
211 $hasEntry = FALSE;
212
213 $cacheEntries = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows(
214 '*',
215 $this->cacheTable,
216 'identifier = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($entryIdentifier, $this->cacheTable) .
217 ' AND (crdate + lifetime >= ' . $GLOBALS['EXEC_TIME'] . ' OR lifetime = 0)'
218 );
219 if ($cacheEntries >= 1) {
220 $hasEntry = TRUE;
221 }
222
223 return $hasEntry;
224 }
225
226 /**
227 * Removes all cache entries matching the specified identifier.
228 * Usually this only affects one entry.
229 *
230 * @param string Specifies the cache entry to remove
231 * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
232 * @author Ingo Renner <ingo@typo3.org>
233 */
234 public function remove($entryIdentifier) {
235 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
236 throw new t3lib_cache_Exception(
237 'No cache frontend has been set via setCache() yet.',
238 1308435812
239 );
240 }
241
242 $entryRemoved = FALSE;
243
244 $res = $GLOBALS['TYPO3_DB']->exec_DELETEquery(
245 $this->cacheTable,
246 'identifier = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($entryIdentifier, $this->cacheTable)
247 );
248
249 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
250 $this->tagsTable,
251 'identifier = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($entryIdentifier, $this->tagsTable)
252 );
253
254 if ($GLOBALS['TYPO3_DB']->sql_affected_rows($res) == 1) {
255 $entryRemoved = TRUE;
256 }
257
258 return $entryRemoved;
259 }
260
261 /**
262 * Finds and returns all cache entries which are tagged by the specified tag.
263 *
264 * @param string The tag to search for
265 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
266 * @author Ingo Renner <ingo@typo3.org>
267 */
268 public function findIdentifiersByTag($tag) {
269 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
270 throw new t3lib_cache_Exception(
271 'No cache frontend has been set via setCache() yet.',
272 1308435813
273 );
274 }
275
276 $cacheEntryIdentifiers = array();
277
278 $cacheEntryIdentifierRows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
279 $this->identifierField,
280 $this->tableList,
281 $this->getQueryForTag($tag) .
282 ' AND ' . $this->tableJoin .
283 ' AND ' . $this->notExpiredStatement,
284 $this->identifierField
285 );
286
287 foreach ($cacheEntryIdentifierRows as $cacheEntryIdentifierRow) {
288 $cacheEntryIdentifiers[$cacheEntryIdentifierRow['identifier']] = $cacheEntryIdentifierRow['identifier'];
289 }
290
291 return $cacheEntryIdentifiers;
292 }
293
294 /**
295 * Removes all cache entries of this cache.
296 *
297 * @return void
298 * @author Ingo Renner <ingo@typo3.org>
299 */
300 public function flush() {
301 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
302 throw new t3lib_cache_Exception(
303 'No cache frontend has been set via setCache() yet.',
304 1308435814
305 );
306 }
307
308 $GLOBALS['TYPO3_DB']->exec_TRUNCATEquery($this->cacheTable);
309 $GLOBALS['TYPO3_DB']->exec_TRUNCATEquery($this->tagsTable);
310 $GLOBALS['TYPO3_DB']->admin_query('DROP TABLE IF EXISTS ' . $this->cacheTable);
311 $GLOBALS['TYPO3_DB']->admin_query('DROP TABLE IF EXISTS ' . $this->tagsTable);
312 $this->createCacheTable();
313 $this->createTagsTable();
314 }
315
316 /**
317 * Removes all cache entries of this cache which are tagged by the specified tag.
318 *
319 * @param string The tag the entries must have
320 * @return void
321 */
322 public function flushByTag($tag) {
323 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
324 throw new t3lib_cache_Exception(
325 'No cache frontend has been set via setCache() yet.',
326 1308435815
327 );
328 }
329
330 $tagsTableWhereClause = $this->getQueryForTag($tag);
331
332 $this->deleteCacheTableRowsByTagsTableWhereClause($tagsTableWhereClause);
333
334 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
335 $this->tagsTable,
336 $tagsTableWhereClause
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 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
348 throw new t3lib_cache_Exception(
349 'No cache frontend has been set via setCache() yet.',
350 1308435816
351 );
352 }
353
354 // Get identifiers of expired cache entries
355 $tagsEntryIdentifierRowsResource = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
356 'identifier',
357 $this->cacheTable,
358 'crdate + lifetime < ' . $GLOBALS['EXEC_TIME'] . ' AND lifetime > 0'
359 );
360
361 $tagsEntryIdentifiers = array();
362 while ($tagsEntryIdentifierRow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($tagsEntryIdentifierRowsResource)) {
363 $tagsEntryIdentifiers[] = $GLOBALS['TYPO3_DB']->fullQuoteStr(
364 $tagsEntryIdentifierRow['identifier'],
365 $this->tagsTable
366 );
367 }
368 $GLOBALS['TYPO3_DB']->sql_free_result($tagsEntryIdentifierRowsResource);
369
370 // Delete tag rows connected to expired cache entries
371 if (count($tagsEntryIdentifiers)) {
372 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
373 $this->tagsTable,
374 'identifier IN (' . implode(', ', $tagsEntryIdentifiers) . ')'
375 );
376 }
377
378 // Delete expired cache rows
379 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
380 $this->cacheTable,
381 'crdate + lifetime < ' . $GLOBALS['EXEC_TIME'] . ' AND lifetime > 0'
382 );
383 }
384
385 /**
386 * Sets the table where the cache entries are stored.
387 *
388 * @deprecated since TYPO3 4.6: The backend calculates the
389 * table name internally, this method does nothing anymore
390 * @param string $cacheTable Table name
391 * @return void
392 * @author Ingo Renner <ingo@typo3.org>
393 */
394 public function setCacheTable($cacheTable) {
395 t3lib_div::logDeprecatedFunction();
396 }
397
398 /**
399 * Returns the table where the cache entries are stored.
400 *
401 * @return string The cache table.
402 * @author Ingo Renner <ingo@typo3.org>
403 */
404 public function getCacheTable() {
405 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
406 throw new t3lib_cache_Exception(
407 'No cache frontend has been set via setCache() yet.',
408 1308435817
409 );
410 }
411
412 return $this->cacheTable;
413 }
414
415 /**
416 * Sets the table where cache tags are stored.
417 *
418 * @deprecated since TYPO3 4.6: The backend calculates the
419 * table name internally, this method does nothing anymore
420 * @param string $tagsTable: Tags table name
421 * @return void
422 */
423 public function setTagsTable($tagsTable) {
424 t3lib_div::logDeprecatedFunction();
425 }
426
427 /**
428 * Gets the table where cache tags are stored.
429 *
430 * @return string Name of the table storing tags
431 */
432 public function getTagsTable() {
433 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
434 throw new t3lib_cache_Exception(
435 'No cache frontend has been set via setCache() yet.',
436 1308435818
437 );
438 }
439
440 return $this->tagsTable;
441 }
442
443 /**
444 * Enable data compression
445 *
446 * @param boolean TRUE to enable compression
447 */
448 public function setCompression($compression) {
449 $this->compression = $compression;
450 }
451
452 /**
453 * Set data compression level.
454 * If compression is enabled and this is not set,
455 * gzcompress default level will be used
456 *
457 * @param integer -1 to 9: Compression level
458 */
459 public function setCompressionLevel($compressionLevel) {
460 if ($compressionLevel >= -1 && $compressionLevel <= 9) {
461 $this->compressionLevel = $compressionLevel;
462 }
463 }
464
465 /**
466 * Create data table of cache
467 *
468 * @return void
469 */
470 protected function createCacheTable() {
471 $sql = file_get_contents(PATH_t3lib . 'cache/backend/resources/dbbackend-layout-cache.sql');
472 $sql = str_replace('###CACHE_TABLE###', $this->cacheTable, $sql);
473 $GLOBALS['TYPO3_DB']->admin_query($sql);
474 }
475
476 /**
477 * Create tags table of cache
478 *
479 * @return void
480 */
481 protected function createTagsTable() {
482 $sql = file_get_contents(PATH_t3lib . 'cache/backend/resources/dbbackend-layout-tags.sql');
483 $sql = str_replace('###TAGS_TABLE###', $this->tagsTable, $sql);
484 $GLOBALS['TYPO3_DB']->admin_query($sql);
485 }
486
487 /**
488 * Gets the query to be used for selecting entries by a tag. The asterisk ("*")
489 * is allowed as a wildcard at the beginning and the end of a tag.
490 *
491 * @param string The tag to search for, the "*" wildcard is supported
492 * @return string the query to be used for selecting entries
493 * @author Oliver Hader <oliver@typo3.org>
494 */
495 protected function getQueryForTag($tag) {
496 if (strpos($tag, '*') === FALSE) {
497 $query = $this->tagsTable . '.tag = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($tag, $this->tagsTable);
498 } else {
499 $patternForLike = $GLOBALS['TYPO3_DB']->escapeStrForLike(
500 $GLOBALS['TYPO3_DB']->quoteStr($tag, $this->tagsTable),
501 $this->tagsTable
502 );
503 $query = $this->tagsTable . '.tag LIKE \'' . $patternForLike . '\'';
504 }
505
506 return $query;
507 }
508
509 /**
510 * Deletes rows in cache table found by where clause on tags table
511 *
512 * @param string The where clause for the tags table
513 * @return void
514 */
515 protected function deleteCacheTableRowsByTagsTableWhereClause($tagsTableWhereClause) {
516 $cacheEntryIdentifierRowsRessource = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
517 'DISTINCT identifier',
518 $this->tagsTable,
519 $tagsTableWhereClause
520 );
521
522 $cacheEntryIdentifiers = array();
523 while ($cacheEntryIdentifierRow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($cacheEntryIdentifierRowsRessource)) {
524 $cacheEntryIdentifiers[] = $GLOBALS['TYPO3_DB']->fullQuoteStr(
525 $cacheEntryIdentifierRow['identifier'],
526 $this->cacheTable
527 );
528 }
529 $GLOBALS['TYPO3_DB']->sql_free_result($cacheEntryIdentifierRowsRessource);
530
531 if (count($cacheEntryIdentifiers)) {
532 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
533 $this->cacheTable,
534 'identifier IN (' . implode(', ', $cacheEntryIdentifiers) . ')'
535 );
536 }
537 }
538 }
539
540
541 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_dbbackend.php'])) {
542 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_dbbackend.php']);
543 }
544
545 ?>