Passed
Pull Request — master (#856)
by
unknown
10:26
created

CacheManager::getInstance()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 31
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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