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