[TASK] Removes extra empty lines
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Cache / CacheManager.php
1 <?php
2 namespace TYPO3\CMS\Core\Cache;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Cache\Exception\NoSuchCacheGroupException;
18
19 /**
20 * The Cache Manager
21 *
22 * This file is a backport from FLOW3
23 * @scope singleton
24 * @api
25 */
26 class CacheManager implements \TYPO3\CMS\Core\SingletonInterface
27 {
28 /**
29 * @var \TYPO3\CMS\Core\Cache\CacheFactory
30 */
31 protected $cacheFactory;
32
33 /**
34 * @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface[]
35 */
36 protected $caches = array();
37
38 /**
39 * @var array
40 */
41 protected $cacheConfigurations = array();
42
43 /**
44 * Used to flush caches of a specific group
45 * is an associative array containing the group identifier as key
46 * and the identifier as an array within that group
47 * groups are set via the cache configurations of each cache.
48 *
49 * @var array
50 */
51 protected $cacheGroups = array();
52
53 /**
54 * @var array Default cache configuration as fallback
55 */
56 protected $defaultCacheConfiguration = array(
57 'frontend' => \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend::class,
58 'backend' => \TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend::class,
59 'options' => array(),
60 'groups' => array('all')
61 );
62
63 /**
64 * @param \TYPO3\CMS\Core\Cache\CacheFactory $cacheFactory
65 * @return void
66 */
67 public function injectCacheFactory(\TYPO3\CMS\Core\Cache\CacheFactory $cacheFactory)
68 {
69 $this->cacheFactory = $cacheFactory;
70 }
71
72 /**
73 * Sets configurations for caches. The key of each entry specifies the
74 * cache identifier and the value is an array of configuration options.
75 * Possible options are:
76 *
77 * frontend
78 * backend
79 * backendOptions
80 *
81 * If one of the options is not specified, the default value is assumed.
82 * Existing cache configurations are preserved.
83 *
84 * @param array $cacheConfigurations The cache configurations to set
85 * @return void
86 * @throws \InvalidArgumentException If $cacheConfigurations is not an array
87 */
88 public function setCacheConfigurations(array $cacheConfigurations)
89 {
90 foreach ($cacheConfigurations as $identifier => $configuration) {
91 if (!is_array($configuration)) {
92 throw new \InvalidArgumentException('The cache configuration for cache "' . $identifier . '" was not an array as expected.', 1231259656);
93 }
94 $this->cacheConfigurations[$identifier] = $configuration;
95 }
96 }
97
98 /**
99 * Registers a cache so it can be retrieved at a later point.
100 *
101 * @param \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache The cache frontend to be registered
102 * @return void
103 * @throws \TYPO3\CMS\Core\Cache\Exception\DuplicateIdentifierException if a cache with the given identifier has already been registered.
104 * @api
105 */
106 public function registerCache(\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache)
107 {
108 $identifier = $cache->getIdentifier();
109 if (isset($this->caches[$identifier])) {
110 throw new \TYPO3\CMS\Core\Cache\Exception\DuplicateIdentifierException('A cache with identifier "' . $identifier . '" has already been registered.', 1203698223);
111 }
112 $this->caches[$identifier] = $cache;
113 }
114
115 /**
116 * Returns the cache specified by $identifier
117 *
118 * @param string $identifier Identifies which cache to return
119 * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface The specified cache frontend
120 * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
121 * @api
122 */
123 public function getCache($identifier)
124 {
125 if ($this->hasCache($identifier) === false) {
126 throw new \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException('A cache with identifier "' . $identifier . '" does not exist.', 1203699034);
127 }
128 if (!isset($this->caches[$identifier])) {
129 $this->createCache($identifier);
130 }
131 return $this->caches[$identifier];
132 }
133
134 /**
135 * Checks if the specified cache has been registered.
136 *
137 * @param string $identifier The identifier of the cache
138 * @return bool TRUE if a cache with the given identifier exists, otherwise FALSE
139 * @api
140 */
141 public function hasCache($identifier)
142 {
143 return isset($this->caches[$identifier]) || isset($this->cacheConfigurations[$identifier]);
144 }
145
146 /**
147 * Flushes all registered caches
148 *
149 * @return void
150 * @api
151 */
152 public function flushCaches()
153 {
154 $this->createAllCaches();
155 foreach ($this->caches as $cache) {
156 $cache->flush();
157 }
158 }
159
160 /**
161 * Flushes all registered caches of a specific group
162 *
163 * @param string $groupIdentifier
164 * @return void
165 * @throws NoSuchCacheGroupException
166 * @api
167 */
168 public function flushCachesInGroup($groupIdentifier)
169 {
170 $this->createAllCaches();
171 if (isset($this->cacheGroups[$groupIdentifier])) {
172 foreach ($this->cacheGroups[$groupIdentifier] as $cacheIdentifier) {
173 if (isset($this->caches[$cacheIdentifier])) {
174 $this->caches[$cacheIdentifier]->flush();
175 }
176 }
177 } else {
178 throw new NoSuchCacheGroupException('No cache in the specified group \'' . $groupIdentifier . '\'', 1390334120);
179 }
180 }
181
182 /**
183 * Flushes entries tagged by the specified tag of all registered
184 * caches of a specific group.
185 *
186 * @param string $groupIdentifier
187 * @param string $tag Tag to search for
188 * @return void
189 * @throws NoSuchCacheGroupException
190 * @api
191 */
192 public function flushCachesInGroupByTag($groupIdentifier, $tag)
193 {
194 $this->createAllCaches();
195 if (isset($this->cacheGroups[$groupIdentifier])) {
196 foreach ($this->cacheGroups[$groupIdentifier] as $cacheIdentifier) {
197 if (isset($this->caches[$cacheIdentifier])) {
198 $this->caches[$cacheIdentifier]->flushByTag($tag);
199 }
200 }
201 } else {
202 throw new NoSuchCacheGroupException('No cache in the specified group \'' . $groupIdentifier . '\'', 1390337129);
203 }
204 }
205
206 /**
207 * Flushes entries tagged by the specified tag of all registered
208 * caches.
209 *
210 * @param string $tag Tag to search for
211 * @return void
212 * @api
213 */
214 public function flushCachesByTag($tag)
215 {
216 $this->createAllCaches();
217 foreach ($this->caches as $cache) {
218 $cache->flushByTag($tag);
219 }
220 }
221
222 /**
223 * TYPO3 v4 note: This method is a direct backport from FLOW3 and currently
224 * unused in TYPO3 v4 context.
225 *
226 * Flushes entries tagged with class names if their class source files have changed.
227 * Also flushes AOP proxy caches if a policy was modified.
228 *
229 * This method is used as a slot for a signal sent by the system file monitor
230 * defined in the bootstrap scripts.
231 *
232 * Note: Policy configuration handling is implemented here as well as other parts
233 * of FLOW3 (like the security framework) are not fully initialized at the
234 * time needed.
235 *
236 * @param string $fileMonitorIdentifier Identifier of the File Monitor
237 * @param array $changedFiles A list of full paths to changed files
238 * @return void
239 */
240 public function flushClassFileCachesByChangedFiles($fileMonitorIdentifier, array $changedFiles)
241 {
242 $modifiedClassNamesWithUnderscores = array();
243 $objectClassesCache = $this->getCache('FLOW3_Object_Classes');
244 $objectConfigurationCache = $this->getCache('FLOW3_Object_Configuration');
245 switch ($fileMonitorIdentifier) {
246 case 'FLOW3_ClassFiles':
247 $modifiedAspectClassNamesWithUnderscores = array();
248 foreach ($changedFiles as $pathAndFilename => $status) {
249 $pathAndFilename = str_replace(FLOW3_PATH_PACKAGES, '', $pathAndFilename);
250 $matches = array();
251 if (preg_match('/[^\\/]+\\/(.+)\\/(Classes|Tests)\\/(.+)\\.php/', $pathAndFilename, $matches) === 1) {
252 $classNameWithUnderscores = str_replace(array('/', '.'), '_', $matches[1] . '_' . ($matches[2] === 'Tests' ? 'Tests_' : '') . $matches[3]);
253 $modifiedClassNamesWithUnderscores[$classNameWithUnderscores] = true;
254 // If an aspect was modified, the whole code cache needs to be flushed, so keep track of them:
255 if (substr($classNameWithUnderscores, -6, 6) === 'Aspect') {
256 $modifiedAspectClassNamesWithUnderscores[$classNameWithUnderscores] = true;
257 }
258 // As long as no modified aspect was found, we are optimistic that only part of the cache needs to be flushed:
259 if (empty($modifiedAspectClassNamesWithUnderscores)) {
260 $objectClassesCache->remove($classNameWithUnderscores);
261 }
262 }
263 }
264 $flushDoctrineProxyCache = false;
265 if (!empty($modifiedClassNamesWithUnderscores)) {
266 $reflectionStatusCache = $this->getCache('FLOW3_Reflection_Status');
267 foreach ($modifiedClassNamesWithUnderscores as $classNameWithUnderscores => $_) {
268 $reflectionStatusCache->remove($classNameWithUnderscores);
269 if ($flushDoctrineProxyCache === false && preg_match('/_Domain_Model_(.+)/', $classNameWithUnderscores) === 1) {
270 $flushDoctrineProxyCache = true;
271 }
272 }
273 $objectConfigurationCache->remove('allCompiledCodeUpToDate');
274 }
275 if (!empty($modifiedAspectClassNamesWithUnderscores)) {
276 $this->systemLogger->log('Aspect classes have been modified, flushing the whole proxy classes cache.', LOG_INFO);
277 $objectClassesCache->flush();
278 }
279 if ($flushDoctrineProxyCache === true) {
280 $this->systemLogger->log('Domain model changes have been detected, triggering Doctrine 2 proxy rebuilding.', LOG_INFO);
281 $objectConfigurationCache->remove('doctrineProxyCodeUpToDate');
282 }
283 break;
284 case 'FLOW3_ConfigurationFiles':
285 $policyChangeDetected = false;
286 $routesChangeDetected = false;
287 foreach ($changedFiles as $pathAndFilename => $_) {
288 $filename = basename($pathAndFilename);
289 if (!in_array($filename, array('Policy.yaml', 'Routes.yaml'))) {
290 continue;
291 }
292 if ($policyChangeDetected === false && $filename === 'Policy.yaml') {
293 $this->systemLogger->log('The security policies have changed, flushing the policy cache.', LOG_INFO);
294 $this->getCache('FLOW3_Security_Policy')->flush();
295 $policyChangeDetected = true;
296 } elseif ($routesChangeDetected === false && $filename === 'Routes.yaml') {
297 $this->systemLogger->log('A Routes.yaml file has been changed, flushing the routing cache.', LOG_INFO);
298 $this->getCache('FLOW3_Mvc_Routing_FindMatchResults')->flush();
299 $this->getCache('FLOW3_Mvc_Routing_Resolve')->flush();
300 $routesChangeDetected = true;
301 }
302 }
303 $this->systemLogger->log('The configuration has changed, triggering an AOP proxy class rebuild.', LOG_INFO);
304 $objectConfigurationCache->remove('allAspectClassesUpToDate');
305 $objectConfigurationCache->remove('allCompiledCodeUpToDate');
306 $objectClassesCache->flush();
307 break;
308 case 'FLOW3_TranslationFiles':
309 foreach ($changedFiles as $pathAndFilename => $status) {
310 $matches = array();
311 if (preg_match('/\\/Translations\\/.+\\.xlf/', $pathAndFilename, $matches) === 1) {
312 $this->systemLogger->log('The localization files have changed, thus flushing the I18n XML model cache.', LOG_INFO);
313 $this->getCache('FLOW3_I18n_XmlModelCache')->flush();
314 break;
315 }
316 }
317 break;
318 }
319 }
320
321 /**
322 * TYPO3 v4 note: This method is a direct backport from FLOW3 and currently
323 * unused in TYPO3 v4 context.
324 *
325 * Renders a tag which can be used to mark a cache entry as "depends on this class".
326 * Whenever the specified class is modified, all cache entries tagged with the
327 * class are flushed.
328 *
329 * If an empty string is specified as class name, the returned tag means
330 * "this cache entry becomes invalid if any of the known classes changes".
331 *
332 * @param string $className The class name
333 * @return string Class Tag
334 * @api
335 */
336 public static function getClassTag($className = '')
337 {
338 return $className === '' ? \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface::TAG_CLASS : \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface::TAG_CLASS . str_replace('\\', '_', $className);
339 }
340
341 /**
342 * Instantiates all registered caches.
343 *
344 * @return void
345 */
346 protected function createAllCaches()
347 {
348 foreach ($this->cacheConfigurations as $identifier => $_) {
349 if (!isset($this->caches[$identifier])) {
350 $this->createCache($identifier);
351 }
352 }
353 }
354
355 /**
356 * Instantiates the cache for $identifier.
357 *
358 * @param string $identifier
359 * @return void
360 */
361 protected function createCache($identifier)
362 {
363 if (isset($this->cacheConfigurations[$identifier]['frontend'])) {
364 $frontend = $this->cacheConfigurations[$identifier]['frontend'];
365 } else {
366 $frontend = $this->defaultCacheConfiguration['frontend'];
367 }
368 if (isset($this->cacheConfigurations[$identifier]['backend'])) {
369 $backend = $this->cacheConfigurations[$identifier]['backend'];
370 } else {
371 $backend = $this->defaultCacheConfiguration['backend'];
372 }
373 if (isset($this->cacheConfigurations[$identifier]['options'])) {
374 $backendOptions = $this->cacheConfigurations[$identifier]['options'];
375 } else {
376 $backendOptions = $this->defaultCacheConfiguration['options'];
377 }
378
379 // Add the cache identifier to the groups that it should be attached to, or use the default ones.
380 if (isset($this->cacheConfigurations[$identifier]['groups']) && is_array($this->cacheConfigurations[$identifier]['groups'])) {
381 $assignedGroups = $this->cacheConfigurations[$identifier]['groups'];
382 } else {
383 $assignedGroups = $this->defaultCacheConfiguration['groups'];
384 }
385 foreach ($assignedGroups as $groupIdentifier) {
386 if (!isset($this->cacheGroups[$groupIdentifier])) {
387 $this->cacheGroups[$groupIdentifier] = array();
388 }
389 $this->cacheGroups[$groupIdentifier][] = $identifier;
390 }
391
392 $this->cacheFactory->create($identifier, $frontend, $backend, $backendOptions);
393 }
394 }