Passed
Pull Request — master (#857)
by Georges
04:15 queued 02:15
created

CacheManager::clearInstances()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 7
rs 10
1
<?php
2
3
/**
4
 *
5
 * This file is part of Phpfastcache.
6
 *
7
 * @license MIT License (MIT)
8
 *
9
 * For full copyright and license information, please see the docs/CREDITS.txt and LICENCE files.
10
 *
11
 * @author Georges.L (Geolim4)  <[email protected]>
12
 * @author Contributors  https://github.com/PHPSocialNetwork/phpfastcache/graphs/contributors
13
 */
14
15
declare(strict_types=1);
16
17
namespace Phpfastcache;
18
19
use Phpfastcache\Config\ConfigurationOption;
20
use Phpfastcache\Config\ConfigurationOptionInterface;
21
use Phpfastcache\Core\Pool\ExtendedCacheItemPoolInterface;
22
use Phpfastcache\Exceptions\PhpfastcacheDriverCheckException;
23
use Phpfastcache\Exceptions\PhpfastcacheDriverException;
24
use Phpfastcache\Exceptions\PhpfastcacheDriverNotFoundException;
25
use Phpfastcache\Exceptions\PhpfastcacheInstanceNotFoundException;
26
use Phpfastcache\Exceptions\PhpfastcacheInvalidArgumentException;
27
use Phpfastcache\Exceptions\PhpfastcacheLogicException;
28
use Phpfastcache\Exceptions\PhpfastcacheUnsupportedOperationException;
29
use Phpfastcache\Helper\UninstanciableObjectTrait;
30
use Phpfastcache\Util\ClassNamespaceResolverTrait;
31
32
/**
33
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
34
 */
35
class CacheManager
36
{
37
    use ClassNamespaceResolverTrait;
38
    use UninstanciableObjectTrait;
39
40
    public const CORE_DRIVER_NAMESPACE = 'Phpfastcache\Drivers\\';
41
42
    protected static ConfigurationOptionInterface $config;
43
44
    protected static string $namespacePath;
45
46
    /**
47
     * @var ExtendedCacheItemPoolInterface[]
48
     */
49
    protected static array $instances = [];
50
51
    /**
52
     * @var string[]
53
     */
54
    protected static array $driverOverrides = [];
55
56
    /**
57
     * @var string[]
58
     */
59
    protected static array $driverCustoms = [];
60
61
    /**
62
     * @param string $instanceId
63
     * @return ExtendedCacheItemPoolInterface
64
     * @throws PhpfastcacheInstanceNotFoundException
65
     */
66
    public static function getInstanceById(string $instanceId): ExtendedCacheItemPoolInterface
67
    {
68
        if (isset(self::$instances[$instanceId])) {
69
            return self::$instances[$instanceId];
70
        }
71
72
        throw new PhpfastcacheInstanceNotFoundException(sprintf('Instance ID %s not found', $instanceId));
73
    }
74
75
    /**
76
     * Return the list of instances
77
     *
78
     * @return ExtendedCacheItemPoolInterface[]
79
     */
80
    public static function getInstances(): array
81
    {
82
        return self::$instances;
83
    }
84
85
    /**
86
     * @param string $driver
87
     * @param ConfigurationOptionInterface|null $config
88
     * @param string|null $instanceId
89
     * @return ExtendedCacheItemPoolInterface
90
     * @throws PhpfastcacheDriverCheckException
91
     * @throws PhpfastcacheDriverException
92
     * @throws PhpfastcacheDriverNotFoundException
93
     * @throws PhpfastcacheLogicException
94
     */
95
    public static function getInstance(string $driver, ?ConfigurationOptionInterface $config = null, ?string $instanceId = null): ExtendedCacheItemPoolInterface
96
    {
97
        if (\class_exists($driver) && \str_starts_with($driver, 'Phpfastcache')) {
98
            $driverClass = $driver;
99
        } else {
100
            $driver = self::normalizeDriverName($driver);
101
            $driverClass = self::validateDriverClass(self::getDriverClass($driver));
102
        }
103
        $config = self::validateConfig($config);
104
        $instanceId = $instanceId ?: self::getInstanceHash($driverClass, $config);
105
106
        if (!isset(self::$instances[$instanceId])) {
107
            if (\is_a($driverClass, ExtendedCacheItemPoolInterface::class, true)) {
108
                if (($configClass = $driverClass::getConfigClass()) !== $config::class) {
109
                    $config = new $configClass($config->toArray());
110
                }
111
                self::$instances[$instanceId] = new $driverClass(
112
                    $config,
113
                    $instanceId,
114
                    EventManager::getInstance()
115
                );
116
            } else {
117
                throw new PhpfastcacheDriverNotFoundException(sprintf(
118
                    'The driver "%s" does not exist or does not implement %s',
119
                    $driver,
120
                    ExtendedCacheItemPoolInterface::class
121
                ));
122
            }
123
        }
124
125
        return self::$instances[$instanceId];
126
    }
127
128
    /**
129
     * @param string $driverClass
130
     * @param ConfigurationOptionInterface $config
131
     * @return string
132
     */
133
    protected static function getInstanceHash(string $driverClass, ConfigurationOptionInterface $config): string
134
    {
135
        return \md5($driverClass . \serialize(
136
            \array_filter(
137
                $config->toArray(),
138
                static fn ($val) => $config->isValueSerializable($val)
139
            )
140
        ));
141
    }
142
143
    /**
144
     * @param ConfigurationOptionInterface|null $config
145
     * @return ConfigurationOptionInterface
146
     * @throws PhpfastcacheLogicException
147
     */
148
    protected static function validateConfig(?ConfigurationOptionInterface $config): ConfigurationOptionInterface
149
    {
150
        if ($config instanceof ConfigurationOptionInterface && $config->isLocked()) {
151
            throw new PhpfastcacheLogicException('You provided an already locked configuration, cannot continue.');
152
        }
153
        return $config ?? self::getDefaultConfig();
154
    }
155
156
    /**
157
     * @return ConfigurationOptionInterface
158
     */
159
    public static function getDefaultConfig(): ConfigurationOptionInterface
160
    {
161
        return self::$config ?? self::$config = new ConfigurationOption();
162
    }
163
164
    /**
165
     * @param string $driverName
166
     * @return string
167
     */
168
    public static function normalizeDriverName(string $driverName): string
169
    {
170
        return \ucfirst(\strtolower(\trim($driverName)));
171
    }
172
173
    /**
174
     * @param string $driverClass
175
     * @return string
176
     * @throws PhpfastcacheDriverException
177
     */
178
    protected static function validateDriverClass(string $driverClass): string
179
    {
180
        if (!\is_a($driverClass, ExtendedCacheItemPoolInterface::class, true)) {
181
            throw new PhpfastcacheDriverException(
182
                \sprintf(
183
                    'Class "%s" does not implement "%s"',
184
                    $driverClass,
185
                    ExtendedCacheItemPoolInterface::class
186
                )
187
            );
188
        }
189
        return $driverClass;
190
    }
191
192
    /**
193
     * @param string $driverName
194
     * @return string
195
     */
196
    public static function getDriverClass(string $driverName): string
197
    {
198
        if (!empty(self::$driverCustoms[$driverName])) {
199
            $driverClass = self::$driverCustoms[$driverName];
200
        } elseif (!empty(self::$driverOverrides[$driverName])) {
201
            $driverClass = self::$driverOverrides[$driverName];
202
        } else {
203
            $driverClass = self::getNamespacePath() . $driverName . '\Driver';
204
        }
205
206
        return $driverClass;
207
    }
208
209
    /**
210
     * @return string
211
     */
212
    public static function getNamespacePath(): string
213
    {
214
        return self::$namespacePath ?? self::getDefaultNamespacePath();
215
    }
216
217
    /**
218
     * @return string
219
     */
220
    public static function getDefaultNamespacePath(): string
221
    {
222
        return self::CORE_DRIVER_NAMESPACE;
223
    }
224
225
    /**
226
     * @return bool
227
     */
228
    public static function clearInstances(): bool
229
    {
230
        self::$instances = [];
231
232
        \gc_collect_cycles();
233
234
        return true;
235
    }
236
237
    /**
238
     * @param ExtendedCacheItemPoolInterface $cachePoolInstance
239
     * @return bool
240
     * @since 7.0.4
241
     */
242
    public static function clearInstance(ExtendedCacheItemPoolInterface $cachePoolInstance): bool
243
    {
244
        $found = false;
245
        self::$instances = \array_filter(
246
            \array_map(
247
                static function (ExtendedCacheItemPoolInterface $cachePool) use ($cachePoolInstance, &$found) {
248
                    if (\spl_object_hash($cachePool) === \spl_object_hash($cachePoolInstance)) {
249
                        $found = true;
250
                        return null;
251
                    }
252
                    return $cachePool;
253
                },
254
                self::$instances
255
            )
256
        );
257
258
        return $found;
259
    }
260
261
    /**
262
     * @param ConfigurationOptionInterface $config
263
     * @throws PhpfastcacheInvalidArgumentException
264
     */
265
    public static function setDefaultConfig(ConfigurationOptionInterface $config): void
266
    {
267
        if (\is_subclass_of($config, ConfigurationOption::class)) {
268
            throw new PhpfastcacheInvalidArgumentException('Default configuration cannot be a child class of ConfigurationOption::class');
269
        }
270
        self::$config = $config;
271
    }
272
273
    /**
274
     * @param string $driverName
275
     * @param string $className
276
     * @return void
277
     * @throws PhpfastcacheLogicException
278
     * @throws PhpfastcacheUnsupportedOperationException
279
     * @throws PhpfastcacheInvalidArgumentException
280
     */
281
    public static function addCustomDriver(string $driverName, string $className): void
282
    {
283
        $driverName = self::normalizeDriverName($driverName);
284
285
        if (empty($driverName)) {
286
            throw new PhpfastcacheInvalidArgumentException("Can't add a custom driver because its name is empty");
287
        }
288
289
        if (!\class_exists($className)) {
290
            throw new PhpfastcacheInvalidArgumentException(
291
                \sprintf("Can't add '%s' because the class '%s' does not exist", $driverName, $className)
292
            );
293
        }
294
295
        if (!empty(self::$driverCustoms[$driverName])) {
296
            throw new PhpfastcacheLogicException(\sprintf("Driver '%s' has been already added", $driverName));
297
        }
298
299
        if (\in_array($driverName, self::getDriverList(), true)) {
300
            throw new PhpfastcacheLogicException(\sprintf("Driver '%s' is already a part of the Phpfastcache core", $driverName));
301
        }
302
303
        self::$driverCustoms[$driverName] = $className;
304
    }
305
306
    /**
307
     * Return the list of available drivers Capitalized
308
     * with optional FQCN as key
309
     *
310
     * @param bool $fqcnAsKey Describe keys with Full Qualified Class Name
311
     * @return string[]
312
     * @throws PhpfastcacheUnsupportedOperationException
313
     */
314
    public static function getDriverList(bool $fqcnAsKey = false): array
315
    {
316
        static $driverList;
317
318
        if (self::getDefaultNamespacePath() === self::getNamespacePath()) {
319
            if ($driverList === null) {
320
                $prefix = self::CORE_DRIVER_NAMESPACE;
321
                $classMap = self::createClassMap(__DIR__ . '/Drivers');
322
                $driverList = [];
323
324
                foreach (\array_keys($classMap) as $class) {
325
                    $driverList[] = \str_replace($prefix, '', \substr($class, 0, \strrpos($class, '\\')));
326
                }
327
328
                $driverList = \array_values(\array_unique($driverList));
329
            }
330
331
            $driverList = \array_merge($driverList, \array_keys(self::$driverCustoms));
332
333
            if ($fqcnAsKey) {
334
                $realDriverList = [];
335
                foreach ($driverList as $driverName) {
336
                    $realDriverList[self::getDriverClass($driverName)] = $driverName;
337
                }
338
                $driverList = $realDriverList;
339
            }
340
341
            \asort($driverList);
342
343
            return $driverList;
344
        }
345
346
        throw new PhpfastcacheUnsupportedOperationException('Cannot get the driver list if the default namespace path has changed.');
347
    }
348
349
    /**
350
     * @param string $driverName
351
     * @return void
352
     * @throws PhpfastcacheLogicException
353
     * @throws PhpfastcacheInvalidArgumentException
354
     */
355
    public static function removeCustomDriver(string $driverName): void
356
    {
357
        $driverName = self::normalizeDriverName($driverName);
358
359
        if (empty($driverName)) {
360
            throw new PhpfastcacheInvalidArgumentException("Can't remove a custom driver because its name is empty");
361
        }
362
363
        if (!isset(self::$driverCustoms[$driverName])) {
364
            throw new PhpfastcacheLogicException(\sprintf("Driver '%s' does not exist", $driverName));
365
        }
366
367
        unset(self::$driverCustoms[$driverName]);
368
    }
369
370
    /**
371
     * @param string $driverName
372
     * @param string $className
373
     * @return void
374
     * @throws PhpfastcacheLogicException
375
     * @throws PhpfastcacheUnsupportedOperationException
376
     * @throws PhpfastcacheInvalidArgumentException
377
     */
378
    public static function addCoreDriverOverride(string $driverName, string $className): void
379
    {
380
        $driverName = self::normalizeDriverName($driverName);
381
382
        if (empty($driverName)) {
383
            throw new PhpfastcacheInvalidArgumentException("Can't add a core driver override because its name is empty");
384
        }
385
386
        if (!\class_exists($className)) {
387
            throw new PhpfastcacheInvalidArgumentException(
388
                \sprintf("Can't override '%s' because the class '%s' does not exist", $driverName, $className)
389
            );
390
        }
391
392
        if (!empty(self::$driverOverrides[$driverName])) {
393
            throw new PhpfastcacheLogicException(\sprintf("Driver '%s' has been already overridden", $driverName));
394
        }
395
396
        if (!\in_array($driverName, self::getDriverList(), true)) {
397
            throw new PhpfastcacheLogicException(\sprintf("Driver '%s' can't be overridden since its not a part of the Phpfastcache core", $driverName));
398
        }
399
400
        if (!\is_subclass_of($className, self::CORE_DRIVER_NAMESPACE . $driverName . '\\Driver', true)) {
401
            throw new PhpfastcacheLogicException(
402
                \sprintf(
403
                    "Can't override '%s' because the class '%s' MUST extend '%s'",
404
                    $driverName,
405
                    $className,
406
                    self::CORE_DRIVER_NAMESPACE . $driverName . '\\Driver'
407
                )
408
            );
409
        }
410
411
        self::$driverOverrides[$driverName] = $className;
412
    }
413
414
    /**
415
     * @param string $driverName
416
     * @return void
417
     * @throws PhpfastcacheLogicException
418
     * @throws PhpfastcacheInvalidArgumentException
419
     */
420
    public static function removeCoreDriverOverride(string $driverName): void
421
    {
422
        $driverName = self::normalizeDriverName($driverName);
423
424
        if (empty($driverName)) {
425
            throw new PhpfastcacheInvalidArgumentException("Can't remove a core driver override because its name is empty");
426
        }
427
428
        if (!isset(self::$driverOverrides[$driverName])) {
429
            throw new PhpfastcacheLogicException(\sprintf("Driver '%s' were not overridden", $driverName));
430
        }
431
432
        unset(self::$driverOverrides[$driverName]);
433
    }
434
}
435