2 /***************************************************************
5 * (c) 2008-2011 Ingo Renner <ingo@typo3.org>
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.
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
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.
22 * This copyright notice MUST APPEAR in all copies of the script!
23 ***************************************************************/
26 * A caching backend which stores cache entries in database tables
29 * @subpackage t3lib_cache
33 class t3lib_cache_backend_DbBackend
extends t3lib_cache_backend_AbstractBackend
{
36 * @var integer Timestamp of 2038-01-01)
38 const FAKED_UNLIMITED_EXPIRE
= 2145909600;
41 * @var string Name of the cache data table
43 protected $cacheTable;
46 * @var string Name of the cache tags table
51 * @var boolean Indicates wether data is compressed or not (requires php zlib)
53 protected $compression = FALSE;
56 * @var integer -1 to 9, indicates zlib compression level: -1 = default level 6, 0 = no compression, 9 maximum compression
58 protected $compressionLevel = -1;
61 * @var string Name of the identifier field, 'table_name.identifier'
63 protected $identifierField;
66 * @var string Name of the expire field, 'table_name.expires'
68 protected $expiresField;
71 * @var integer Maximum lifetime to stay with expire field below FAKED_UNLIMITED_LIFETIME
73 protected $maximumLifetime;
76 * @var string SQL where for a not expired entry
78 protected $notExpiredStatement;
81 * @var string Opposite of notExpiredStatement
83 protected $expiredStatement;
86 * @var string Data and tags table name comma separated
91 * @var string Join condition for data and tags table
96 * Set cache frontend instance and calculate data and tags table name
98 * @param t3lib_cache_frontend_Frontend $cache The frontend for this backend
100 * @author Christian Kuhn <lolli@schwarzbu.ch>
103 public function setCache(t3lib_cache_frontend_Frontend
$cache) {
104 parent
::setCache($cache);
106 $this->cacheTable
= 'cachingframework_' . $this->cacheIdentifier
;
107 $this->tagsTable
= 'cachingframework_' . $this->cacheIdentifier
. '_tags';
108 $this->initializeCommonReferences();
112 * Initializes common references used in this backend.
116 protected function initializeCommonReferences() {
117 $this->identifierField
= $this->cacheTable
. '.identifier';
118 $this->expiresField
= $this->cacheTable
. '.expires';
119 $this->maximumLifetime
= self
::FAKED_UNLIMITED_EXPIRE
- $GLOBALS['EXEC_TIME'];
120 $this->tableList
= $this->cacheTable
. ', ' . $this->tagsTable
;
121 $this->tableJoin
= $this->identifierField
. ' = ' . $this->tagsTable
. '.identifier';
122 $this->expiredStatement
= $this->expiresField
. ' < ' . $GLOBALS['EXEC_TIME'];
123 $this->notExpiredStatement
= $this->expiresField
. ' >= ' . $GLOBALS['EXEC_TIME'];
127 * Saves data in a cache file.
129 * @param string An identifier for this specific cache entry
130 * @param string The data to be stored
131 * @param array Tags to associate with this cache entry
132 * @param integer Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited liftime.
134 * @throws t3lib_cache_Exception if no cache frontend has been set.
135 * @throws t3lib_cache_exception_InvalidData if the data to be stored is not a string.
136 * @author Ingo Renner <ingo@typo3.org>
138 public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
139 $this->throwExceptionIfFrontendDoesNotExist();
141 if (!is_string($data)) {
142 throw new t3lib_cache_exception_InvalidData(
143 'The specified data is of type "' . gettype($data) . '" but a string is expected.',
148 if (is_null($lifetime)) {
149 $lifetime = $this->defaultLifetime
;
151 if ($lifetime === 0 ||
$lifetime > $this->maximumLifetime
) {
152 $lifetime = $this->maximumLifetime
;
154 $expires = $GLOBALS['EXEC_TIME'] +
$lifetime;
156 $this->remove($entryIdentifier);
158 if ($this->compression
) {
159 $data = gzcompress($data, $this->compressionLevel
);
162 $GLOBALS['TYPO3_DB']->exec_INSERTquery(
165 'identifier' => $entryIdentifier,
166 'expires' => $expires,
173 $fields[] = 'identifier';
177 foreach ($tags as $tag) {
179 $tagRow[] = $entryIdentifier;
181 $tagRows[] = $tagRow;
184 $GLOBALS['TYPO3_DB']->exec_INSERTmultipleRows(
193 * Loads data from a cache file.
195 * @param string An identifier which describes the cache entry to load
196 * @return mixed The cache entry's data as a string or FALSE if the cache entry could not be loaded
197 * @author Ingo Renner <ingo@typo3.org>
199 public function get($entryIdentifier) {
200 $this->throwExceptionIfFrontendDoesNotExist();
204 $cacheEntry = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
207 'identifier = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($entryIdentifier, $this->cacheTable
) .
208 ' AND ' . $this->notExpiredStatement
211 if (is_array($cacheEntry)) {
212 $cacheEntry = $cacheEntry['content'];
215 if ($this->compression
&& strlen($cacheEntry)) {
216 $cacheEntry = gzuncompress($cacheEntry);
223 * Checks if a cache entry with the specified identifier exists.
225 * @param string Specifies the identifier to check for existence
226 * @return boolean TRUE if such an entry exists, FALSE if not
227 * @author Ingo Renner <ingo@typo3.org>
229 public function has($entryIdentifier) {
230 $this->throwExceptionIfFrontendDoesNotExist();
234 $cacheEntries = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows(
237 'identifier = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($entryIdentifier, $this->cacheTable
) .
238 ' AND ' . $this->notExpiredStatement
240 if ($cacheEntries >= 1) {
248 * Removes all cache entries matching the specified identifier.
249 * Usually this only affects one entry.
251 * @param string Specifies the cache entry to remove
252 * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
253 * @author Ingo Renner <ingo@typo3.org>
255 public function remove($entryIdentifier) {
256 $this->throwExceptionIfFrontendDoesNotExist();
258 $entryRemoved = FALSE;
260 $res = $GLOBALS['TYPO3_DB']->exec_DELETEquery(
262 'identifier = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($entryIdentifier, $this->cacheTable
)
265 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
267 'identifier = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($entryIdentifier, $this->tagsTable
)
270 if ($GLOBALS['TYPO3_DB']->sql_affected_rows($res) == 1) {
271 $entryRemoved = TRUE;
274 return $entryRemoved;
278 * Finds and returns all cache entries which are tagged by the specified tag.
280 * @param string The tag to search for
281 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
282 * @author Ingo Renner <ingo@typo3.org>
284 public function findIdentifiersByTag($tag) {
285 $this->throwExceptionIfFrontendDoesNotExist();
287 $cacheEntryIdentifiers = array();
289 $cacheEntryIdentifierRows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
290 $this->identifierField
,
292 $this->tagsTable
. '.tag = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($tag, $this->tagsTable
) .
293 ' AND ' . $this->tableJoin
.
294 ' AND ' . $this->notExpiredStatement
,
295 $this->identifierField
298 foreach ($cacheEntryIdentifierRows as $cacheEntryIdentifierRow) {
299 $cacheEntryIdentifiers[$cacheEntryIdentifierRow['identifier']] = $cacheEntryIdentifierRow['identifier'];
302 return $cacheEntryIdentifiers;
306 * Removes all cache entries of this cache.
309 * @author Ingo Renner <ingo@typo3.org>
311 public function flush() {
312 $this->throwExceptionIfFrontendDoesNotExist();
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();
323 * Removes all cache entries of this cache which are tagged by the specified tag.
325 * @param string The tag the entries must have
328 public function flushByTag($tag) {
329 $this->throwExceptionIfFrontendDoesNotExist();
331 $tagsTableWhereClause = $this->tagsTable
. '.tag = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($tag, $this->tagsTable
);
333 $this->deleteCacheTableRowsByTagsTableWhereClause($tagsTableWhereClause);
335 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
337 $tagsTableWhereClause
342 * Does garbage collection
345 * @author Ingo Renner <ingo@typo3.org>
347 public function collectGarbage() {
348 $this->throwExceptionIfFrontendDoesNotExist();
350 // Get identifiers of expired cache entries
351 $tagsEntryIdentifierRowsResource = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
354 $this->expiredStatement
357 $tagsEntryIdentifiers = array();
358 while ($tagsEntryIdentifierRow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($tagsEntryIdentifierRowsResource)) {
359 $tagsEntryIdentifiers[] = $GLOBALS['TYPO3_DB']->fullQuoteStr(
360 $tagsEntryIdentifierRow['identifier'],
364 $GLOBALS['TYPO3_DB']->sql_free_result($tagsEntryIdentifierRowsResource);
366 // Delete tag rows connected to expired cache entries
367 if (count($tagsEntryIdentifiers)) {
368 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
370 'identifier IN (' . implode(', ', $tagsEntryIdentifiers) . ')'
374 // Delete expired cache rows
375 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
377 $this->expiredStatement
382 * Sets the table where the cache entries are stored.
384 * @deprecated since TYPO3 4.6: The backend calculates the
385 * table name internally, this method does nothing anymore
386 * @param string $cacheTable Table name
388 * @author Ingo Renner <ingo@typo3.org>
390 public function setCacheTable($cacheTable) {
391 t3lib_div
::logDeprecatedFunction();
395 * Returns the table where the cache entries are stored.
397 * @return string The cache table.
398 * @author Ingo Renner <ingo@typo3.org>
400 public function getCacheTable() {
401 $this->throwExceptionIfFrontendDoesNotExist();
403 return $this->cacheTable
;
407 * Sets the table where cache tags are stored.
409 * @deprecated since TYPO3 4.6: The backend calculates the
410 * table name internally, this method does nothing anymore
411 * @param string $tagsTable: Tags table name
414 public function setTagsTable($tagsTable) {
415 t3lib_div
::logDeprecatedFunction();
419 * Gets the table where cache tags are stored.
421 * @return string Name of the table storing tags
423 public function getTagsTable() {
424 $this->throwExceptionIfFrontendDoesNotExist();
426 return $this->tagsTable
;
430 * Enable data compression
432 * @param boolean TRUE to enable compression
434 public function setCompression($compression) {
435 $this->compression
= $compression;
439 * Set data compression level.
440 * If compression is enabled and this is not set,
441 * gzcompress default level will be used
443 * @param integer -1 to 9: Compression level
445 public function setCompressionLevel($compressionLevel) {
446 if ($compressionLevel >= -1 && $compressionLevel <= 9) {
447 $this->compressionLevel
= $compressionLevel;
452 * Check if required frontend instance exists
454 * @throws t3lib_cache_Exception If there is no frontend instance in $this->cache
457 protected function throwExceptionIfFrontendDoesNotExist() {
458 if (!$this->cache
instanceof t3lib_cache_frontend_Frontend
) {
459 throw new t3lib_cache_Exception(
460 'No cache frontend has been set via setCache() yet.',
467 * Create data table of cache
471 protected function createCacheTable() {
472 $sql = file_get_contents(PATH_t3lib
. 'cache/backend/resources/dbbackend-layout-cache.sql');
473 $sql = str_replace('###CACHE_TABLE###', $this->cacheTable
, $sql);
474 $GLOBALS['TYPO3_DB']->admin_query($sql);
478 * Create tags table of cache
482 protected function createTagsTable() {
483 $sql = file_get_contents(PATH_t3lib
. 'cache/backend/resources/dbbackend-layout-tags.sql');
484 $sql = str_replace('###TAGS_TABLE###', $this->tagsTable
, $sql);
485 $GLOBALS['TYPO3_DB']->admin_query($sql);
489 * Deletes rows in cache table found by where clause on tags table
491 * @param string The where clause for the tags table
494 protected function deleteCacheTableRowsByTagsTableWhereClause($tagsTableWhereClause) {
495 $cacheEntryIdentifierRowsRessource = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
496 'DISTINCT identifier',
498 $tagsTableWhereClause
501 $cacheEntryIdentifiers = array();
502 while ($cacheEntryIdentifierRow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($cacheEntryIdentifierRowsRessource)) {
503 $cacheEntryIdentifiers[] = $GLOBALS['TYPO3_DB']->fullQuoteStr(
504 $cacheEntryIdentifierRow['identifier'],
508 $GLOBALS['TYPO3_DB']->sql_free_result($cacheEntryIdentifierRowsRessource);
510 if (count($cacheEntryIdentifiers)) {
511 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
513 'identifier IN (' . implode(', ', $cacheEntryIdentifiers) . ')'
520 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE
]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_dbbackend.php'])) {
521 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE
]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_dbbackend.php']);