bb6321e36dfc96c557dd155d46225feaf1c0d0fc
[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\Backend\BackendInterface;
18 use TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend;
19 use TYPO3\CMS\Core\Cache\Exception\DuplicateIdentifierException;
20 use TYPO3\CMS\Core\Cache\Exception\InvalidBackendException;
21 use TYPO3\CMS\Core\Cache\Exception\InvalidCacheException;
22 use TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException;
23 use TYPO3\CMS\Core\Cache\Exception\NoSuchCacheGroupException;
24 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
25 use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend;
26 use TYPO3\CMS\Core\SingletonInterface;
27
28 /**
29 * The Cache Manager
30 *
31 * This file is a backport from FLOW3
32 * @scope singleton
33 * @api
34 */
35 class CacheManager implements SingletonInterface
36 {
37 /**
38 * @var FrontendInterface[]
39 */
40 protected $caches = [];
41
42 /**
43 * @var array
44 */
45 protected $cacheConfigurations = [];
46
47 /**
48 * Used to flush caches of a specific group
49 * is an associative array containing the group identifier as key
50 * and the identifier as an array within that group
51 * groups are set via the cache configurations of each cache.
52 *
53 * @var array
54 */
55 protected $cacheGroups = [];
56
57 /**
58 * @var array Default cache configuration as fallback
59 */
60 protected $defaultCacheConfiguration = [
61 'frontend' => VariableFrontend::class,
62 'backend' => Typo3DatabaseBackend::class,
63 'options' => [],
64 'groups' => ['all']
65 ];
66
67 /**
68 * Sets configurations for caches. The key of each entry specifies the
69 * cache identifier and the value is an array of configuration options.
70 * Possible options are:
71 *
72 * frontend
73 * backend
74 * backendOptions
75 *
76 * If one of the options is not specified, the default value is assumed.
77 * Existing cache configurations are preserved.
78 *
79 * @param array $cacheConfigurations The cache configurations to set
80 * @return void
81 * @throws \InvalidArgumentException If $cacheConfigurations is not an array
82 */
83 public function setCacheConfigurations(array $cacheConfigurations)
84 {
85 foreach ($cacheConfigurations as $identifier => $configuration) {
86 if (!is_array($configuration)) {
87 throw new \InvalidArgumentException('The cache configuration for cache "' . $identifier . '" was not an array as expected.', 1231259656);
88 }
89 $this->cacheConfigurations[$identifier] = $configuration;
90 }
91 }
92
93 /**
94 * Registers a cache so it can be retrieved at a later point.
95 *
96 * @param FrontendInterface $cache The cache frontend to be registered
97 * @return void
98 * @throws DuplicateIdentifierException if a cache with the given identifier has already been registered.
99 * @api
100 */
101 public function registerCache(FrontendInterface $cache)
102 {
103 $identifier = $cache->getIdentifier();
104 if (isset($this->caches[$identifier])) {
105 throw new DuplicateIdentifierException('A cache with identifier "' . $identifier . '" has already been registered.', 1203698223);
106 }
107 $this->caches[$identifier] = $cache;
108 }
109
110 /**
111 * Returns the cache specified by $identifier
112 *
113 * @param string $identifier Identifies which cache to return
114 * @return FrontendInterface The specified cache frontend
115 * @throws NoSuchCacheException
116 * @api
117 */
118 public function getCache($identifier)
119 {
120 if ($this->hasCache($identifier) === false) {
121 throw new NoSuchCacheException('A cache with identifier "' . $identifier . '" does not exist.', 1203699034);
122 }
123 if (!isset($this->caches[$identifier])) {
124 $this->createCache($identifier);
125 }
126 return $this->caches[$identifier];
127 }
128
129 /**
130 * Checks if the specified cache has been registered.
131 *
132 * @param string $identifier The identifier of the cache
133 * @return bool TRUE if a cache with the given identifier exists, otherwise FALSE
134 * @api
135 */
136 public function hasCache($identifier)
137 {
138 return isset($this->caches[$identifier]) || isset($this->cacheConfigurations[$identifier]);
139 }
140
141 /**
142 * Flushes all registered caches
143 *
144 * @return void
145 * @api
146 */
147 public function flushCaches()
148 {
149 $this->createAllCaches();
150 foreach ($this->caches as $cache) {
151 $cache->flush();
152 }
153 }
154
155 /**
156 * Flushes all registered caches of a specific group
157 *
158 * @param string $groupIdentifier
159 * @return void
160 * @throws NoSuchCacheGroupException
161 * @api
162 */
163 public function flushCachesInGroup($groupIdentifier)
164 {
165 $this->createAllCaches();
166 if (!isset($this->cacheGroups[$groupIdentifier])) {
167 throw new NoSuchCacheGroupException('No cache in the specified group \'' . $groupIdentifier . '\'', 1390334120);
168 }
169 foreach ($this->cacheGroups[$groupIdentifier] as $cacheIdentifier) {
170 if (isset($this->caches[$cacheIdentifier])) {
171 $this->caches[$cacheIdentifier]->flush();
172 }
173 }
174 }
175
176 /**
177 * Flushes entries tagged by the specified tag of all registered
178 * caches of a specific group.
179 *
180 * @param string $groupIdentifier
181 * @param string|array $tag Tag to search for
182 * @return void
183 * @throws NoSuchCacheGroupException
184 * @api
185 */
186 public function flushCachesInGroupByTag($groupIdentifier, $tag)
187 {
188 if (empty($tag)) {
189 return;
190 }
191 $this->createAllCaches();
192 if (!isset($this->cacheGroups[$groupIdentifier])) {
193 throw new NoSuchCacheGroupException('No cache in the specified group \'' . $groupIdentifier . '\'', 1390337129);
194 }
195 foreach ($this->cacheGroups[$groupIdentifier] as $cacheIdentifier) {
196 if (isset($this->caches[$cacheIdentifier])) {
197 $this->caches[$cacheIdentifier]->flushByTag($tag);
198 }
199 }
200 }
201
202 /**
203 * Flushes entries tagged by any of the specified tags in all registered
204 * caches of a specific group.
205 *
206 * @param string $groupIdentifier
207 * @param string[] $tag Tags to search for
208 * @return void
209 * @throws NoSuchCacheGroupException
210 * @api
211 */
212 public function flushCachesInGroupByTags($groupIdentifier, array $tags)
213 {
214 if (empty($tag)) {
215 return;
216 }
217 $this->createAllCaches();
218 if (!isset($this->cacheGroups[$groupIdentifier])) {
219 throw new NoSuchCacheGroupException('No cache in the specified group \'' . $groupIdentifier . '\'', 1390337130);
220 }
221 foreach ($this->cacheGroups[$groupIdentifier] as $cacheIdentifier) {
222 if (isset($this->caches[$cacheIdentifier])) {
223 $this->caches[$cacheIdentifier]->flushByTags($tags);
224 }
225 }
226 }
227
228 /**
229 * Flushes entries tagged by the specified tag of all registered
230 * caches.
231 *
232 * @param string $tag Tag to search for
233 * @return void
234 * @api
235 */
236 public function flushCachesByTag($tag)
237 {
238 $this->createAllCaches();
239 foreach ($this->caches as $cache) {
240 $cache->flushByTag($tag);
241 }
242 }
243
244 /**
245 * Flushes entries tagged by any of the specified tags in all registered caches.
246 *
247 * @param string[] $tag Tags to search for
248 * @return void
249 * @api
250 */
251 public function flushCachesByTags(array $tags)
252 {
253 $this->createAllCaches();
254 foreach ($this->caches as $cache) {
255 $cache->flushByTags($tags);
256 }
257 }
258
259 /**
260 * Instantiates all registered caches.
261 *
262 * @return void
263 */
264 protected function createAllCaches()
265 {
266 foreach ($this->cacheConfigurations as $identifier => $_) {
267 if (!isset($this->caches[$identifier])) {
268 $this->createCache($identifier);
269 }
270 }
271 }
272
273 /**
274 * Instantiates the cache for $identifier.
275 *
276 * @param string $identifier
277 * @throws DuplicateIdentifierException
278 * @throws InvalidBackendException
279 * @throws InvalidCacheException
280 */
281 protected function createCache($identifier)
282 {
283 if (isset($this->cacheConfigurations[$identifier]['frontend'])) {
284 $frontend = $this->cacheConfigurations[$identifier]['frontend'];
285 } else {
286 $frontend = $this->defaultCacheConfiguration['frontend'];
287 }
288 if (isset($this->cacheConfigurations[$identifier]['backend'])) {
289 $backend = $this->cacheConfigurations[$identifier]['backend'];
290 } else {
291 $backend = $this->defaultCacheConfiguration['backend'];
292 }
293 if (isset($this->cacheConfigurations[$identifier]['options'])) {
294 $backendOptions = $this->cacheConfigurations[$identifier]['options'];
295 } else {
296 $backendOptions = $this->defaultCacheConfiguration['options'];
297 }
298
299 // Add the cache identifier to the groups that it should be attached to, or use the default ones.
300 if (isset($this->cacheConfigurations[$identifier]['groups']) && is_array($this->cacheConfigurations[$identifier]['groups'])) {
301 $assignedGroups = $this->cacheConfigurations[$identifier]['groups'];
302 } else {
303 $assignedGroups = $this->defaultCacheConfiguration['groups'];
304 }
305 foreach ($assignedGroups as $groupIdentifier) {
306 if (!isset($this->cacheGroups[$groupIdentifier])) {
307 $this->cacheGroups[$groupIdentifier] = [];
308 }
309 $this->cacheGroups[$groupIdentifier][] = $identifier;
310 }
311
312 // New operator used on purpose: This class is required early during
313 // bootstrap before makeInstance() is properly set up
314 $backend = '\\' . ltrim($backend, '\\');
315 $backendInstance = new $backend('production', $backendOptions);
316 if (!$backendInstance instanceof BackendInterface) {
317 throw new InvalidBackendException('"' . $backend . '" is not a valid cache backend object.', 1464550977);
318 }
319 if (is_callable([$backendInstance, 'initializeObject'])) {
320 $backendInstance->initializeObject();
321 }
322
323 // New used on purpose, see comment above
324 $frontendInstance = new $frontend($identifier, $backendInstance);
325 if (!$frontendInstance instanceof FrontendInterface) {
326 throw new InvalidCacheException('"' . $frontend . '" is not a valid cache frontend object.', 1464550984);
327 }
328 if (is_callable([$frontendInstance, 'initializeObject'])) {
329 $frontendInstance->initializeObject();
330 }
331
332 $this->registerCache($frontendInstance);
333 }
334 }