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