Completed
Push — master ( 84cbe1...688425 )
by
unknown
17:01
created

CacheManager::createAllCaches()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 3
nc 3
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
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
16
namespace TYPO3\CMS\Core\Cache;
17
18
use TYPO3\CMS\Core\Cache\Backend\BackendInterface;
19
use TYPO3\CMS\Core\Cache\Backend\NullBackend;
20
use TYPO3\CMS\Core\Cache\Backend\TransientMemoryBackend;
21
use TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend;
22
use TYPO3\CMS\Core\Cache\Exception\DuplicateIdentifierException;
23
use TYPO3\CMS\Core\Cache\Exception\InvalidBackendException;
24
use TYPO3\CMS\Core\Cache\Exception\InvalidCacheException;
25
use TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException;
26
use TYPO3\CMS\Core\Cache\Exception\NoSuchCacheGroupException;
27
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
28
use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend;
29
use TYPO3\CMS\Core\SingletonInterface;
30
31
/**
32
 * The Cache Manager
33
 */
34
class CacheManager implements SingletonInterface
35
{
36
    /**
37
     * @var FrontendInterface[]
38
     */
39
    protected $caches = [];
40
41
    /**
42
     * @var array
43
     */
44
    protected $cacheConfigurations = [];
45
46
    /**
47
     * Used to flush caches of a specific group
48
     * is an associative array containing the group identifier as key
49
     * and the identifier as an array within that group
50
     * groups are set via the cache configurations of each cache.
51
     *
52
     * @var array
53
     */
54
    protected $cacheGroups = [];
55
56
    /**
57
     * @var array Default cache configuration as fallback
58
     */
59
    protected $defaultCacheConfiguration = [
60
        'frontend' => VariableFrontend::class,
61
        'backend' => Typo3DatabaseBackend::class,
62
        'options' => [],
63
        'groups' => ['all']
64
    ];
65
66
    /**
67
     * @var bool
68
     */
69
    protected $disableCaching = false;
70
71
    /**
72
     * @param bool $disableCaching
73
     */
74
    public function __construct(bool $disableCaching = false)
75
    {
76
        $this->disableCaching = $disableCaching;
77
    }
78
79
    /**
80
     * Sets configurations for caches. The key of each entry specifies the
81
     * cache identifier and the value is an array of configuration options.
82
     * Possible options are:
83
     *
84
     * frontend
85
     * backend
86
     * backendOptions
87
     *
88
     * If one of the options is not specified, the default value is assumed.
89
     * Existing cache configurations are preserved.
90
     *
91
     * @param array $cacheConfigurations The cache configurations to set
92
     * @throws \InvalidArgumentException If $cacheConfigurations is not an array
93
     */
94
    public function setCacheConfigurations(array $cacheConfigurations)
95
    {
96
        $newConfiguration = [];
97
        $migratedConfiguration = [];
98
        foreach ($cacheConfigurations as $identifier => $configuration) {
99
            if (empty($identifier)) {
100
                throw new \InvalidArgumentException('A cache identifier was not set.', 1596980032);
101
            }
102
            if (!is_array($configuration)) {
103
                throw new \InvalidArgumentException('The cache configuration for cache "' . $identifier . '" was not an array as expected.', 1231259656);
104
            }
105
            // Fallback layer, will be removed in TYPO3 v11.0.
106
            if (strpos($identifier, 'cache_') === 0) {
107
                $identifier = substr($identifier, 6);
108
                if (empty($identifier)) {
109
                    throw new \InvalidArgumentException('A cache identifier was not set.', 1596980033);
110
                }
111
                trigger_error('Accessing a cache with the "cache_" prefix as in "' . $identifier . '" is not necessary anymore, and should be called without the cache prefix.', E_USER_DEPRECATED);
112
                $migratedConfiguration[$identifier] = $configuration;
113
            } else {
114
                $newConfiguration[$identifier] = $configuration;
115
            }
116
        }
117
        $this->cacheConfigurations = array_replace_recursive($newConfiguration, $migratedConfiguration);
118
    }
119
120
    /**
121
     * Registers a cache so it can be retrieved at a later point.
122
     *
123
     * @param FrontendInterface $cache The cache frontend to be registered
124
     * @param array $groups Cache groups to be associated to the cache
125
     * @throws DuplicateIdentifierException if a cache with the given identifier has already been registered.
126
     */
127
    public function registerCache(FrontendInterface $cache, array $groups = [])
128
    {
129
        $identifier = $cache->getIdentifier();
130
        if (isset($this->caches[$identifier])) {
131
            throw new DuplicateIdentifierException('A cache with identifier "' . $identifier . '" has already been registered.', 1203698223);
132
        }
133
        $this->caches[$identifier] = $cache;
134
        foreach ($groups as $groupIdentifier) {
135
            $this->cacheGroups[$groupIdentifier][] = $identifier;
136
        }
137
    }
138
139
    /**
140
     * Returns the cache specified by $identifier
141
     *
142
     * @param string $identifier Identifies which cache to return
143
     * @return FrontendInterface The specified cache frontend
144
     * @throws NoSuchCacheException
145
     */
146
    public function getCache($identifier)
147
    {
148
        // Fallback layer, will be removed in TYPO3 v11.0.
149
        if (strpos($identifier, 'cache_') === 0) {
150
            trigger_error('Accessing a cache with the "cache_" prefix as in "' . $identifier . '" is not necessary anymore, and should be called without the cache prefix.', E_USER_DEPRECATED);
151
            $identifier = substr($identifier, 6);
152
        }
153
        if ($this->hasCache($identifier) === false) {
154
            throw new NoSuchCacheException('A cache with identifier "' . $identifier . '" does not exist.', 1203699034);
155
        }
156
        if (!isset($this->caches[$identifier])) {
157
            $this->createCache($identifier);
158
        }
159
        return $this->caches[$identifier];
160
    }
161
162
    /**
163
     * Checks if the specified cache has been registered.
164
     *
165
     * @param string $identifier The identifier of the cache
166
     * @return bool TRUE if a cache with the given identifier exists, otherwise FALSE
167
     */
168
    public function hasCache($identifier)
169
    {
170
        // Fallback layer, will be removed in TYPO3 v11.0.
171
        if (strpos($identifier, 'cache_') === 0) {
172
            trigger_error('Accessing a cache with the "cache_" prefix as in "' . $identifier . '" is not necessary anymore, and should be called without the cache prefix.', E_USER_DEPRECATED);
173
            $identifier = substr($identifier, 6);
174
        }
175
        return isset($this->caches[$identifier]) || isset($this->cacheConfigurations[$identifier]);
176
    }
177
178
    /**
179
     * Flushes all registered caches
180
     */
181
    public function flushCaches()
182
    {
183
        $this->createAllCaches();
184
        foreach ($this->caches as $cache) {
185
            $cache->flush();
186
        }
187
    }
188
189
    /**
190
     * Flushes all registered caches of a specific group
191
     *
192
     * @param string $groupIdentifier
193
     * @throws NoSuchCacheGroupException
194
     */
195
    public function flushCachesInGroup($groupIdentifier)
196
    {
197
        $this->createAllCaches();
198
        if (!isset($this->cacheGroups[$groupIdentifier])) {
199
            throw new NoSuchCacheGroupException('No cache in the specified group \'' . $groupIdentifier . '\'', 1390334120);
200
        }
201
        foreach ($this->cacheGroups[$groupIdentifier] as $cacheIdentifier) {
202
            if (isset($this->caches[$cacheIdentifier])) {
203
                $this->caches[$cacheIdentifier]->flush();
204
            }
205
        }
206
    }
207
208
    /**
209
     * Flushes entries tagged by the specified tag of all registered
210
     * caches of a specific group.
211
     *
212
     * @param string $groupIdentifier
213
     * @param string|array $tag Tag to search for
214
     * @throws NoSuchCacheGroupException
215
     */
216
    public function flushCachesInGroupByTag($groupIdentifier, $tag)
217
    {
218
        if (empty($tag)) {
219
            return;
220
        }
221
        $this->createAllCaches();
222
        if (!isset($this->cacheGroups[$groupIdentifier])) {
223
            throw new NoSuchCacheGroupException('No cache in the specified group \'' . $groupIdentifier . '\'', 1390337129);
224
        }
225
        foreach ($this->cacheGroups[$groupIdentifier] as $cacheIdentifier) {
226
            if (isset($this->caches[$cacheIdentifier])) {
227
                $this->caches[$cacheIdentifier]->flushByTag($tag);
0 ignored issues
show
Bug introduced by
It seems like $tag can also be of type array; however, parameter $tag of TYPO3\CMS\Core\Cache\Fro...Interface::flushByTag() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

227
                $this->caches[$cacheIdentifier]->flushByTag(/** @scrutinizer ignore-type */ $tag);
Loading history...
228
            }
229
        }
230
    }
231
232
    /**
233
     * Flushes entries tagged by any of the specified tags in all registered
234
     * caches of a specific group.
235
     *
236
     * @param string $groupIdentifier
237
     * @param string[] $tags Tags to search for
238
     * @throws NoSuchCacheGroupException
239
     */
240
    public function flushCachesInGroupByTags($groupIdentifier, array $tags)
241
    {
242
        if (empty($tags)) {
243
            return;
244
        }
245
        $this->createAllCaches();
246
        if (!isset($this->cacheGroups[$groupIdentifier])) {
247
            throw new NoSuchCacheGroupException('No cache in the specified group \'' . $groupIdentifier . '\'', 1390337130);
248
        }
249
        foreach ($this->cacheGroups[$groupIdentifier] as $cacheIdentifier) {
250
            if (isset($this->caches[$cacheIdentifier])) {
251
                $this->caches[$cacheIdentifier]->flushByTags($tags);
252
            }
253
        }
254
    }
255
256
    /**
257
     * Flushes entries tagged by the specified tag of all registered
258
     * caches.
259
     *
260
     * @param string $tag Tag to search for
261
     */
262
    public function flushCachesByTag($tag)
263
    {
264
        $this->createAllCaches();
265
        foreach ($this->caches as $cache) {
266
            $cache->flushByTag($tag);
267
        }
268
    }
269
270
    /**
271
     * Flushes entries tagged by any of the specified tags in all registered caches.
272
     *
273
     * @param string[] $tags Tags to search for
274
     */
275
    public function flushCachesByTags(array $tags)
276
    {
277
        $this->createAllCaches();
278
        foreach ($this->caches as $cache) {
279
            $cache->flushByTags($tags);
280
        }
281
    }
282
283
    /**
284
     * Instantiates all registered caches.
285
     */
286
    protected function createAllCaches()
287
    {
288
        foreach ($this->cacheConfigurations as $identifier => $_) {
289
            if (!isset($this->caches[$identifier])) {
290
                $this->createCache($identifier);
291
            }
292
        }
293
    }
294
295
    /**
296
     * Instantiates the cache for $identifier.
297
     *
298
     * @param string $identifier
299
     * @throws DuplicateIdentifierException
300
     * @throws InvalidBackendException
301
     * @throws InvalidCacheException
302
     */
303
    protected function createCache($identifier)
304
    {
305
        if (isset($this->cacheConfigurations[$identifier]['frontend'])) {
306
            $frontend = $this->cacheConfigurations[$identifier]['frontend'];
307
        } else {
308
            $frontend = $this->defaultCacheConfiguration['frontend'];
309
        }
310
        if (isset($this->cacheConfigurations[$identifier]['backend'])) {
311
            $backend = $this->cacheConfigurations[$identifier]['backend'];
312
        } else {
313
            $backend = $this->defaultCacheConfiguration['backend'];
314
        }
315
        if (isset($this->cacheConfigurations[$identifier]['options'])) {
316
            $backendOptions = $this->cacheConfigurations[$identifier]['options'];
317
        } else {
318
            $backendOptions = $this->defaultCacheConfiguration['options'];
319
        }
320
321
        if ($this->disableCaching && $backend !== TransientMemoryBackend::class) {
322
            $backend = NullBackend::class;
323
            $backendOptions = [];
324
        }
325
326
        // Add the cache identifier to the groups that it should be attached to, or use the default ones.
327
        if (isset($this->cacheConfigurations[$identifier]['groups']) && is_array($this->cacheConfigurations[$identifier]['groups'])) {
328
            $assignedGroups = $this->cacheConfigurations[$identifier]['groups'];
329
        } else {
330
            $assignedGroups = $this->defaultCacheConfiguration['groups'];
331
        }
332
        foreach ($assignedGroups as $groupIdentifier) {
333
            if (!isset($this->cacheGroups[$groupIdentifier])) {
334
                $this->cacheGroups[$groupIdentifier] = [];
335
            }
336
            $this->cacheGroups[$groupIdentifier][] = $identifier;
337
        }
338
339
        // New operator used on purpose: This class is required early during
340
        // bootstrap before makeInstance() is properly set up
341
        $backend = '\\' . ltrim($backend, '\\');
342
        $backendInstance = new $backend('production', $backendOptions);
343
        if (!$backendInstance instanceof BackendInterface) {
344
            throw new InvalidBackendException('"' . $backend . '" is not a valid cache backend object.', 1464550977);
345
        }
346
        if (is_callable([$backendInstance, 'initializeObject'])) {
347
            $backendInstance->initializeObject();
0 ignored issues
show
Bug introduced by
The method initializeObject() does not exist on TYPO3\CMS\Core\Cache\Backend\BackendInterface. It seems like you code against a sub-type of TYPO3\CMS\Core\Cache\Backend\BackendInterface such as TYPO3\CMS\Core\Cache\Backend\RedisBackend or TYPO3\CMS\Core\Cache\Backend\PdoBackend or TYPO3\CMS\Core\Cache\Backend\MemcachedBackend or TYPO3\CMS\Core\Cache\Backend\MemcachedBackend or TYPO3\CMS\Core\Tests\Uni...InitializeObjectFixture or TYPO3\CMS\Core\Cache\Backend\RedisBackend or TYPO3\CMS\Core\Cache\Backend\PdoBackend or TYPO3\CMS\Core\Cache\Backend\MemcachedBackend. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

347
            $backendInstance->/** @scrutinizer ignore-call */ 
348
                              initializeObject();
Loading history...
348
        }
349
350
        // New used on purpose, see comment above
351
        $frontendInstance = new $frontend($identifier, $backendInstance);
352
        if (!$frontendInstance instanceof FrontendInterface) {
353
            throw new InvalidCacheException('"' . $frontend . '" is not a valid cache frontend object.', 1464550984);
354
        }
355
        if (is_callable([$frontendInstance, 'initializeObject'])) {
356
            $frontendInstance->initializeObject();
0 ignored issues
show
Bug introduced by
The method initializeObject() does not exist on TYPO3\CMS\Core\Cache\Frontend\FrontendInterface. It seems like you code against a sub-type of TYPO3\CMS\Core\Cache\Frontend\FrontendInterface such as TYPO3\CMS\Core\Tests\Uni...InitializeObjectFixture. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

356
            $frontendInstance->/** @scrutinizer ignore-call */ 
357
                               initializeObject();
Loading history...
357
        }
358
359
        $this->registerCache($frontendInstance);
360
    }
361
}
362