‪TYPO3CMS  ‪main
CacheLifetimeCalculator.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\EventDispatcher\EventDispatcherInterface;
21 use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
22 use Symfony\Component\DependencyInjection\Attribute\Autowire;
31 
40 #[Autoconfigure(public: true)]
42 {
43  protected int ‪$defaultCacheTimeout = 86400;
44 
45  public function ‪__construct(
46  #[Autowire(service: 'cache.runtime')]
47  protected readonly ‪FrontendInterface $runtimeCache,
48  protected readonly EventDispatcherInterface $eventDispatcher,
49  protected readonly ‪ConnectionPool $connectionPool
50  ) {}
51 
55  public function ‪calculateLifetimeForPage(int $pageId, array $pageRecord, array $renderingInstructions, int $defaultCacheTimoutInSeconds, ‪Context $context): int
56  {
57  $cachedCacheLifetimeIdentifier = 'cacheLifeTimeForPage_' . $pageId;
58  $cachedCacheLifetime = $this->runtimeCache->get($cachedCacheLifetimeIdentifier);
59  if ($cachedCacheLifetime !== false) {
60  return $cachedCacheLifetime;
61  }
62  if ($pageRecord['cache_timeout'] ?? false) {
63  // Cache period was set for the page:
64  $cacheTimeout = (int)$pageRecord['cache_timeout'];
65  } else {
66  // Cache period was set via TypoScript "config.cache_period",
67  // otherwise it's the default of 24 hours
68  $cacheTimeout = $defaultCacheTimoutInSeconds ?: (int)($renderingInstructions['cache_period'] ?? $this->defaultCacheTimeout);
69  }
70  // A pages endtime limits the upper bound of the maxmium cache lifetime
71  $pageEndtime = (int)($pageRecord['endtime'] ?? 0);
72  if ($pageEndtime > 0) {
73  $cacheTimeout = min($cacheTimeout, $pageEndtime - ‪$GLOBALS['EXEC_TIME']);
74  }
75  if (!empty($renderingInstructions['cache_clearAtMidnight'])) {
76  $timeOutTime = ‪$GLOBALS['EXEC_TIME'] + $cacheTimeout;
77  $midnightTime = mktime(0, 0, 0, (int)date('m', $timeOutTime), (int)date('d', $timeOutTime), (int)date('Y', $timeOutTime));
78  // If the midnight time of the expire-day is greater than the current time,
79  // we may set the timeOutTime to the new midnighttime.
80  if ($midnightTime > ‪$GLOBALS['EXEC_TIME']) {
81  $cacheTimeout = $midnightTime - ‪$GLOBALS['EXEC_TIME'];
82  }
83  }
84 
85  // Calculate the timeout time for records on the page and adjust cache timeout if necessary
86  // Get the configuration
87  $tablesToConsider = $this->‪getCurrentPageCacheConfiguration($pageId, $renderingInstructions);
88 
89  // Get the time, rounded to the minute (do not pollute MySQL cache!)
90  // It is ok that we do not take seconds into account here because this
91  // value will be subtracted later. So we never get the time "before"
92  // the cache change.
93  $currentTimestamp = (int)‪$GLOBALS['ACCESS_TIME'];
94  $cacheTimeout = min($this->‪calculatePageCacheLifetime($tablesToConsider, $currentTimestamp), $cacheTimeout);
95 
97  $cacheTimeout,
98  $pageId,
99  $pageRecord,
100  $renderingInstructions,
101  $context
102  );
103  $event = $this->eventDispatcher->dispatch($event);
104  $cacheTimeout = $event->getCacheLifetime();
105  $this->runtimeCache->set($cachedCacheLifetimeIdentifier, $cacheTimeout);
106  return $cacheTimeout;
107  }
108 
114  protected function ‪calculatePageCacheLifetime(array $tablesToConsider, int $currentTimestamp): int
115  {
116  $result = PHP_INT_MAX;
117  // Find timeout by checking every table
118  foreach ($tablesToConsider as $tableDef) {
119  $result = min($result, $this->‪getFirstTimeValueForRecord($tableDef, $currentTimestamp));
120  }
121  // We return + 1 second just to ensure that cache is definitely regenerated
122  return $result === PHP_INT_MAX ? PHP_INT_MAX : $result - $currentTimestamp + 1;
123  }
124 
142  protected function ‪getCurrentPageCacheConfiguration(int $currentPageId, array $renderingInstructions): array
143  {
144  $result = ['tt_content:' . $currentPageId];
145  if (isset($renderingInstructions['cache.'][$currentPageId])) {
146  $result = array_merge($result, ‪GeneralUtility::trimExplode(',', str_replace(':current', ':' . $currentPageId, $renderingInstructions['cache.'][$currentPageId])));
147  }
148  if (isset($renderingInstructions['cache.']['all'])) {
149  $result = array_merge($result, ‪GeneralUtility::trimExplode(',', str_replace(':current', ':' . $currentPageId, $renderingInstructions['cache.']['all'])));
150  }
151  return array_unique($result);
152  }
153 
163  protected function ‪getFirstTimeValueForRecord(string $tableDef, int $currentTimestamp): int
164  {
165  $result = PHP_INT_MAX;
166  [$tableName, $pid] = ‪GeneralUtility::trimExplode(':', $tableDef);
167  if (empty($tableName) || empty($pid)) {
168  throw new \InvalidArgumentException('Unexpected value for parameter $tableDef. Expected <tablename>:<pid>, got \'' . htmlspecialchars($tableDef) . '\'.', 1307190365);
169  }
170 
171  $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
172  $queryBuilder->getRestrictions()
173  ->removeByType(StartTimeRestriction::class)
174  ->removeByType(EndTimeRestriction::class);
175  $timeFields = [];
176  $timeConditions = $queryBuilder->expr()->or();
177  foreach (['starttime', 'endtime'] as $field) {
178  if (isset($GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns'][$field])) {
179  $timeFields[$field] = $GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns'][$field];
180  $queryBuilder->addSelectLiteral(
181  'MIN('
182  . 'CASE WHEN '
183  . $queryBuilder->expr()->lte(
184  $timeFields[$field],
185  $queryBuilder->createNamedParameter($currentTimestamp, Connection::PARAM_INT)
186  )
187  . ' THEN NULL ELSE ' . $queryBuilder->quoteIdentifier($timeFields[$field]) . ' END'
188  . ') AS ' . $queryBuilder->quoteIdentifier($timeFields[$field])
189  );
190  $timeConditions = $timeConditions->with(
191  $queryBuilder->expr()->gt(
192  $timeFields[$field],
193  $queryBuilder->createNamedParameter($currentTimestamp, Connection::PARAM_INT)
194  )
195  );
196  }
197  }
198 
199  // if starttime or endtime are defined, evaluate them
200  if (!empty($timeFields)) {
201  // find the timestamp, when the current page's content changes the next time
202  $row = $queryBuilder
203  ->from($tableName)
204  ->where(
205  $queryBuilder->expr()->eq(
206  'pid',
207  $queryBuilder->createNamedParameter($pid, ‪Connection::PARAM_INT)
208  ),
209  $timeConditions
210  )
211  ->executeQuery()
212  ->fetchAssociative();
213 
214  ‪if ($row) {
215  foreach ($timeFields as $timeField => $_) {
216  // if a MIN value is found, take it into account for the
217  // cache lifetime we have to filter out start/endtimes < $currentTimestamp,
218  // as the SQL query also returns rows with starttime < $currentTimestamp
219  // and endtime > $currentTimestamp (and using a starttime from the past
220  // would be wrong)
221  if ($row[$timeField] !== null && (int)$row[$timeField] > $currentTimestamp) {
222  $result = min($result, (int)$row[$timeField]);
223  }
224  }
225  }
226  }
227 
228  return $result;
229  }
230 }
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:52
‪TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction
Definition: EndTimeRestriction.php:27
‪TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction
Definition: StartTimeRestriction.php:27
‪TYPO3\CMS\Frontend\Cache\CacheLifetimeCalculator\__construct
‪__construct(#[Autowire(service:'cache.runtime')] protected readonly FrontendInterface $runtimeCache, protected readonly EventDispatcherInterface $eventDispatcher, protected readonly ConnectionPool $connectionPool)
Definition: CacheLifetimeCalculator.php:45
‪if
‪if(PHP_SAPI !=='cli')
Definition: splitAcceptanceTests.php:34
‪TYPO3\CMS\Frontend\Cache
Definition: CacheInstruction.php:18
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:54
‪TYPO3\CMS\Frontend\Cache\CacheLifetimeCalculator\getCurrentPageCacheConfiguration
‪array getCurrentPageCacheConfiguration(int $currentPageId, array $renderingInstructions)
Definition: CacheLifetimeCalculator.php:142
‪TYPO3\CMS\Frontend\Cache\CacheLifetimeCalculator
Definition: CacheLifetimeCalculator.php:42
‪TYPO3\CMS\Frontend\Cache\CacheLifetimeCalculator\$defaultCacheTimeout
‪int $defaultCacheTimeout
Definition: CacheLifetimeCalculator.php:43
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:41
‪TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
Definition: FrontendInterface.php:22
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Frontend\Cache\CacheLifetimeCalculator\calculateLifetimeForPage
‪calculateLifetimeForPage(int $pageId, array $pageRecord, array $renderingInstructions, int $defaultCacheTimoutInSeconds, Context $context)
Definition: CacheLifetimeCalculator.php:55
‪TYPO3\CMS\Frontend\Cache\CacheLifetimeCalculator\getFirstTimeValueForRecord
‪int getFirstTimeValueForRecord(string $tableDef, int $currentTimestamp)
Definition: CacheLifetimeCalculator.php:163
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Frontend\Event\ModifyCacheLifetimeForPageEvent
Definition: ModifyCacheLifetimeForPageEvent.php:27
‪TYPO3\CMS\Frontend\Cache\CacheLifetimeCalculator\calculatePageCacheLifetime
‪int calculatePageCacheLifetime(array $tablesToConsider, int $currentTimestamp)
Definition: CacheLifetimeCalculator.php:114
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:822