‪TYPO3CMS  ‪main
Typo3DatabaseBackend.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
25 
30 {
34  public const ‪FAKED_UNLIMITED_EXPIRE = 2145909600;
38  protected ‪$cacheTable;
39 
43  protected ‪$tagsTable;
44 
48  protected ‪$compression = false;
49 
53  protected ‪$compressionLevel = -1;
54 
58  protected ‪$maximumLifetime;
59 
66  {
67  parent::setCache(‪$cache);
68  $this->cacheTable = 'cache_' . ‪$this->cacheIdentifier;
69  $this->tagsTable = 'cache_' . $this->cacheIdentifier . '_tags';
70  $this->maximumLifetime = self::FAKED_UNLIMITED_EXPIRE - ‪$GLOBALS['EXEC_TIME'];
71  }
72 
83  public function set($entryIdentifier, $data, array $tags = [], $lifetime = null)
84  {
86  if (!is_string($data)) {
87  throw new ‪InvalidDataException(
88  'The specified data is of type "' . gettype($data) . '" but a string is expected.',
89  1236518298
90  );
91  }
92  if ($lifetime === null) {
93  $lifetime = ‪$this->defaultLifetime;
94  }
95  if ($lifetime === 0 || $lifetime > $this->maximumLifetime) {
96  $lifetime = ‪$this->maximumLifetime;
97  }
98  $expires = ‪$GLOBALS['EXEC_TIME'] + $lifetime;
99  $this->remove($entryIdentifier);
100  if ($this->compression) {
101  $data = gzcompress($data, $this->compressionLevel);
102  }
103  GeneralUtility::makeInstance(ConnectionPool::class)
104  ->getConnectionForTable($this->cacheTable)
105  ->insert(
106  $this->cacheTable,
107  [
108  'identifier' => $entryIdentifier,
109  'expires' => $expires,
110  'content' => $data,
111  ],
112  [
113  'content' => ‪Connection::PARAM_LOB,
114  ]
115  );
116  if (!empty($tags)) {
117  $tagRows = [];
118  foreach ($tags as $tag) {
119  $tagRows[] = [$entryIdentifier, $tag];
120  }
121  GeneralUtility::makeInstance(ConnectionPool::class)
122  ->getConnectionForTable($this->tagsTable)
123  ->bulkInsert($this->tagsTable, $tagRows, ['identifier', 'tag'], ['identifier' => ‪Connection::PARAM_STR, 'tag' => ‪Connection::PARAM_STR]);
124  }
125  }
126 
133  public function get($entryIdentifier)
134  {
136  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
137  ->getQueryBuilderForTable($this->cacheTable);
138  $cacheRow = $queryBuilder->select('content')
139  ->from($this->cacheTable)
140  ->where(
141  $queryBuilder->expr()->eq(
142  'identifier',
143  $queryBuilder->createNamedParameter($entryIdentifier)
144  ),
145  $queryBuilder->expr()->gte(
146  'expires',
147  $queryBuilder->createNamedParameter(‪$GLOBALS['EXEC_TIME'], ‪Connection::PARAM_INT)
148  )
149  )
150  ->executeQuery()
151  ->fetchAssociative();
152  $content = '';
153  if (!empty($cacheRow)) {
154  $content = $cacheRow['content'];
155  }
156  if ($this->compression && (string)$content !== '') {
157  $content = gzuncompress($content);
158  }
159  return !empty($cacheRow) ? $content : false;
160  }
161 
168  public function ‪has($entryIdentifier)
169  {
171  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
172  ->getQueryBuilderForTable($this->cacheTable);
173  $count = $queryBuilder->count('*')
174  ->from($this->cacheTable)
175  ->where(
176  $queryBuilder->expr()->eq(
177  'identifier',
178  $queryBuilder->createNamedParameter($entryIdentifier)
179  ),
180  $queryBuilder->expr()->gte(
181  'expires',
182  $queryBuilder->createNamedParameter(‪$GLOBALS['EXEC_TIME'], ‪Connection::PARAM_INT)
183  )
184  )
185  ->executeQuery()
186  ->fetchOne();
187  return (bool)$count;
188  }
189 
197  public function remove($entryIdentifier)
198  {
200  $numberOfRowsRemoved = GeneralUtility::makeInstance(ConnectionPool::class)
201  ->getConnectionForTable($this->cacheTable)
202  ->delete(
203  $this->cacheTable,
204  ['identifier' => $entryIdentifier],
205  ['identifier' => ‪Connection::PARAM_STR]
206  );
207  GeneralUtility::makeInstance(ConnectionPool::class)
208  ->getConnectionForTable($this->tagsTable)
209  ->delete(
210  $this->tagsTable,
211  ['identifier' => $entryIdentifier],
212  ['identifier' => ‪Connection::PARAM_STR]
213  );
214  return (bool)$numberOfRowsRemoved;
215  }
216 
223  public function ‪findIdentifiersByTag($tag)
224  {
226  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
227  ->getQueryBuilderForTable($this->tagsTable);
228  $result = $queryBuilder->select($this->cacheTable . '.identifier')
229  ->from($this->cacheTable)
230  ->from($this->tagsTable)
231  ->where(
232  $queryBuilder->expr()->eq($this->cacheTable . '.identifier', $queryBuilder->quoteIdentifier($this->tagsTable . '.identifier')),
233  $queryBuilder->expr()->eq(
234  $this->tagsTable . '.tag',
235  $queryBuilder->createNamedParameter($tag)
236  ),
237  $queryBuilder->expr()->gte(
238  $this->cacheTable . '.expires',
239  $queryBuilder->createNamedParameter(‪$GLOBALS['EXEC_TIME'], ‪Connection::PARAM_INT)
240  )
241  )
242  ->groupBy($this->cacheTable . '.identifier')
243  ->executeQuery();
244  $identifiers = $result->fetchFirstColumn();
245  return array_combine($identifiers, $identifiers);
246  }
247 
251  public function ‪flush()
252  {
254  GeneralUtility::makeInstance(ConnectionPool::class)
255  ->getConnectionForTable($this->cacheTable)
256  ->truncate($this->cacheTable);
257  GeneralUtility::makeInstance(ConnectionPool::class)
258  ->getConnectionForTable($this->tagsTable)
259  ->truncate($this->tagsTable);
260  }
261 
268  public function ‪flushByTags(array $tags)
269  {
271 
272  if (empty($tags)) {
273  return;
274  }
275 
276  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->cacheTable);
277 
278  // A large set of tags was detected. Process it in chunks to guard against exceeding
279  // maximum SQL query limits.
280  if (count($tags) > 100) {
281  $chunks = array_chunk($tags, 100);
282  array_walk($chunks, $this->‪flushByTags(...));
283  return;
284  }
285  // VERY simple quoting of tags is sufficient here for performance. Tags are already
286  // validated to not contain any bad characters, e.g. they are automatically generated
287  // inside this class and suffixed with a pure integer enforced by DB.
288  $quotedTagList = array_map(static function (string $value): string {
289  return '\'' . $value . '\'';
290  }, $tags);
291 
292  $queryBuilder = $connection->createQueryBuilder();
293  $result = $queryBuilder->select('identifier')
294  ->from($this->tagsTable)
295  ->where('tag IN (' . implode(',', $quotedTagList) . ')')
296  // group by is like DISTINCT and used here to suppress possible duplicate identifiers
297  ->groupBy('identifier')
298  ->executeQuery();
299  $cacheEntryIdentifiers = $result->fetchFirstColumn();
300  $quotedIdentifiers = $queryBuilder->createNamedParameter($cacheEntryIdentifiers, ‪Connection::PARAM_STR_ARRAY);
301  $queryBuilder->delete($this->cacheTable)
302  ->where($queryBuilder->expr()->in('identifier', $quotedIdentifiers))
303  ->executeStatement();
304  $queryBuilder->delete($this->tagsTable)
305  ->where($queryBuilder->expr()->in('identifier', $quotedIdentifiers))
306  ->executeStatement();
307  }
308 
314  public function ‪flushByTag($tag)
315  {
317 
318  if (empty($tag)) {
319  return;
320  }
321 
322  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->cacheTable);
323 
324  $quotedTag = '\'' . $tag . '\'';
325 
326  $queryBuilder = $connection->createQueryBuilder();
327  $result = $queryBuilder->select('identifier')
328  ->from($this->tagsTable)
329  ->where('tag = ' . $quotedTag)
330  // group by is like DISTINCT and used here to suppress possible duplicate identifiers
331  ->groupBy('identifier')
332  ->executeQuery();
333  $cacheEntryIdentifiers = $result->fetchFirstColumn();
334  $quotedIdentifiers = $queryBuilder->createNamedParameter($cacheEntryIdentifiers, ‪Connection::PARAM_STR_ARRAY);
335  $queryBuilder->delete($this->cacheTable)
336  ->where($queryBuilder->expr()->in('identifier', $quotedIdentifiers))
337  ->executeStatement();
338  $queryBuilder->delete($this->tagsTable)
339  ->where($queryBuilder->expr()->in('identifier', $quotedIdentifiers))
340  ->executeStatement();
341  }
342 
346  public function ‪collectGarbage()
347  {
349 
350  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->cacheTable);
351  $queryBuilder = $connection->createQueryBuilder();
352  $result = $queryBuilder->select('identifier')
353  ->from($this->cacheTable)
354  ->where($queryBuilder->expr()->lt(
355  'expires',
356  $queryBuilder->createNamedParameter(‪$GLOBALS['EXEC_TIME'], ‪Connection::PARAM_INT)
357  ))
358  // group by is like DISTINCT and used here to suppress possible duplicate identifiers
359  ->groupBy('identifier')
360  ->executeQuery();
361 
362  // Get identifiers of expired cache entries
363  $cacheEntryIdentifiers = $result->fetchFirstColumn();
364  if (!empty($cacheEntryIdentifiers)) {
365  // Delete tag rows connected to expired cache entries
366  $quotedIdentifiers = $queryBuilder->createNamedParameter($cacheEntryIdentifiers, ‪Connection::PARAM_STR_ARRAY);
367  $queryBuilder->delete($this->tagsTable)
368  ->where($queryBuilder->expr()->in('identifier', $quotedIdentifiers))
369  ->executeStatement();
370  }
371  $queryBuilder->delete($this->cacheTable)
372  ->where($queryBuilder->expr()->lt(
373  'expires',
374  $queryBuilder->createNamedParameter(‪$GLOBALS['EXEC_TIME'], ‪Connection::PARAM_INT)
375  ))
376  ->executeStatement();
377 
378  // Find out which "orphaned" tags rows exists that have no cache row and delete those, too.
379  $queryBuilder = $connection->createQueryBuilder();
380  $result = $queryBuilder->select('tags.identifier')
381  ->from($this->tagsTable, 'tags')
382  ->leftJoin(
383  'tags',
384  $this->cacheTable,
385  'cache',
386  $queryBuilder->expr()->eq('tags.identifier', $queryBuilder->quoteIdentifier('cache.identifier'))
387  )
388  ->where($queryBuilder->expr()->isNull('cache.identifier'))
389  ->groupBy('tags.identifier')
390  ->executeQuery();
391  $tagsEntryIdentifiers = $result->fetchFirstColumn();
392 
393  if (!empty($tagsEntryIdentifiers)) {
394  $quotedIdentifiers = $queryBuilder->createNamedParameter($tagsEntryIdentifiers, ‪Connection::PARAM_STR_ARRAY);
395  $queryBuilder->delete($this->tagsTable)
396  ->where($queryBuilder->expr()->in('identifier', $quotedIdentifiers))
397  ->executeStatement();
398  }
399  }
400 
406  public function ‪getCacheTable()
407  {
409  return ‪$this->cacheTable;
410  }
411 
417  public function ‪getTagsTable()
418  {
420  return ‪$this->tagsTable;
421  }
422 
428  public function ‪setCompression(‪$compression)
429  {
430  $this->compression = ‪$compression;
431  }
432 
441  {
442  if (‪$compressionLevel >= -1 && ‪$compressionLevel <= 9) {
443  $this->compressionLevel = ‪$compressionLevel;
444  }
445  }
446 
452  protected function ‪throwExceptionIfFrontendDoesNotExist()
453  {
454  if (!$this->cache instanceof FrontendInterface) {
455  throw new Exception('No cache frontend has been set via setCache() yet.', 1236518288);
456  }
457  }
458 
466  public function ‪getTableDefinitions()
467  {
468  $cacheTableSql = (string)file_get_contents(
470  'Resources/Private/Sql/Cache/Backend/Typo3DatabaseBackendCache.sql'
471  );
472  $requiredTableStructures = str_replace('###CACHE_TABLE###', $this->cacheTable, $cacheTableSql) . LF . LF;
473  $tagsTableSql = (string)file_get_contents(
475  'Resources/Private/Sql/Cache/Backend/Typo3DatabaseBackendTags.sql'
476  );
477  $requiredTableStructures .= str_replace('###TAGS_TABLE###', $this->tagsTable, $tagsTableSql) . LF;
478  return $requiredTableStructures;
479  }
480 }
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\flush
‪flush()
Definition: Typo3DatabaseBackend.php:246
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\$cacheTable
‪string $cacheTable
Definition: Typo3DatabaseBackend.php:37
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:52
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\findIdentifiersByTag
‪array findIdentifiersByTag($tag)
Definition: Typo3DatabaseBackend.php:218
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\has
‪bool has($entryIdentifier)
Definition: Typo3DatabaseBackend.php:163
‪TYPO3\CMS\Core\Cache\Backend\AbstractBackend\$cache
‪FrontendInterface $cache
Definition: AbstractBackend.php:38
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\$compression
‪bool $compression
Definition: Typo3DatabaseBackend.php:45
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\setCompression
‪setCompression($compression)
Definition: Typo3DatabaseBackend.php:423
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\getTableDefinitions
‪string getTableDefinitions()
Definition: Typo3DatabaseBackend.php:461
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\collectGarbage
‪collectGarbage()
Definition: Typo3DatabaseBackend.php:341
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\flushByTag
‪flushByTag($tag)
Definition: Typo3DatabaseBackend.php:309
‪TYPO3\CMS\Core\Cache\Backend\TaggableBackendInterface
Definition: TaggableBackendInterface.php:22
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\setCache
‪setCache(FrontendInterface $cache)
Definition: Typo3DatabaseBackend.php:60
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend
Definition: Typo3DatabaseBackend.php:30
‪TYPO3\CMS\Core\Cache\Backend\AbstractBackend\$defaultLifetime
‪int $defaultLifetime
Definition: AbstractBackend.php:57
‪TYPO3\CMS\Core\Database\Connection\PARAM_STR
‪const PARAM_STR
Definition: Connection.php:57
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\$maximumLifetime
‪int $maximumLifetime
Definition: Typo3DatabaseBackend.php:53
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\flushByTags
‪flushByTags(array $tags)
Definition: Typo3DatabaseBackend.php:263
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility
Definition: ExtensionManagementUtility.php:32
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\throwExceptionIfFrontendDoesNotExist
‪throwExceptionIfFrontendDoesNotExist()
Definition: Typo3DatabaseBackend.php:447
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\extPath
‪static extPath(string $key, string $script='')
Definition: ExtensionManagementUtility.php:82
‪TYPO3\CMS\Core\Cache\Exception
Definition: DuplicateIdentifierException.php:16
‪TYPO3\CMS\Core\Database\Connection\PARAM_STR_ARRAY
‪const PARAM_STR_ARRAY
Definition: Connection.php:77
‪TYPO3\CMS\Core\Cache\Exception\InvalidDataException
Definition: InvalidDataException.php:23
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\getCacheTable
‪string getCacheTable()
Definition: Typo3DatabaseBackend.php:401
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:41
‪TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
Definition: FrontendInterface.php:22
‪TYPO3\CMS\Core\Cache\Backend\AbstractBackend\$cacheIdentifier
‪string $cacheIdentifier
Definition: AbstractBackend.php:42
‪TYPO3\CMS\Core\Cache\Backend\AbstractBackend
Definition: AbstractBackend.php:28
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\FAKED_UNLIMITED_EXPIRE
‪const FAKED_UNLIMITED_EXPIRE
Definition: Typo3DatabaseBackend.php:34
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\setCompressionLevel
‪setCompressionLevel($compressionLevel)
Definition: Typo3DatabaseBackend.php:435
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\$compressionLevel
‪int $compressionLevel
Definition: Typo3DatabaseBackend.php:49
‪TYPO3\CMS\Core\Cache\Backend
Definition: AbstractBackend.php:16
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\getTagsTable
‪string getTagsTable()
Definition: Typo3DatabaseBackend.php:412
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend\$tagsTable
‪string $tagsTable
Definition: Typo3DatabaseBackend.php:41
‪TYPO3\CMS\Core\Cache\Exception
Definition: Exception.php:21
‪TYPO3\CMS\Core\Database\Connection\PARAM_LOB
‪const PARAM_LOB
Definition: Connection.php:62