Passed
Pull Request — master (#654)
by Georges
03:34 queued 01:42
created

CacheManager::standardizeDriverName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 *
4
 * This file is part of phpFastCache.
5
 *
6
 * @license MIT License (MIT)
7
 *
8
 * For full copyright and license information, please see the docs/CREDITS.txt file.
9
 *
10
 * @author Khoa Bui (khoaofgod)  <[email protected]> https://www.phpfastcache.com
11
 * @author Georges.L (Geolim4)  <[email protected]>
12
 *
13
 */
14
declare(strict_types=1);
15
16
namespace Phpfastcache;
17
18
use Phpfastcache\Config\ConfigurationOption;
19
use Phpfastcache\Core\Pool\ExtendedCacheItemPoolInterface;
20
use Phpfastcache\Exceptions\{
21
    PhpfastcacheDriverCheckException, PhpfastcacheDriverException, PhpfastcacheDriverNotFoundException, PhpfastcacheExceptionInterface, PhpfastcacheInstanceNotFoundException, PhpfastcacheInvalidArgumentException, PhpfastcacheInvalidConfigurationException, PhpfastcacheLogicException, PhpfastcacheRootException, PhpfastcacheUnsupportedOperationException
22
};
23
use Phpfastcache\Util\ClassNamespaceResolverTrait;
24
25
/**
26
 * Class CacheManager
27
 * @package phpFastCache
28
 *
29
 * @method static ExtendedCacheItemPoolInterface Apc() Apc($config = []) Return a driver "Apc" instance
30
 * @method static ExtendedCacheItemPoolInterface Apcu() Apcu($config = []) Return a driver "Apcu" instance
31
 * @method static ExtendedCacheItemPoolInterface Cassandra() Cassandra($config = []) Return a driver "Cassandra" instance
32
 * @method static ExtendedCacheItemPoolInterface Cookie() Cookie($config = []) Return a driver "Cookie" instance
33
 * @method static ExtendedCacheItemPoolInterface Couchbase() Couchbase($config = []) Return a driver "Couchbase" instance
34
 * @method static ExtendedCacheItemPoolInterface Couchdb() Couchdb($config = []) Return a driver "Couchdb" instance
35
 * @method static ExtendedCacheItemPoolInterface Devnull() Devnull($config = []) Return a driver "Devnull" instance
36
 * @method static ExtendedCacheItemPoolInterface Files() Files($config = []) Return a driver "files" instance
37
 * @method static ExtendedCacheItemPoolInterface Leveldb() Leveldb($config = []) Return a driver "Leveldb" instance
38
 * @method static ExtendedCacheItemPoolInterface Memcache() Memcache($config = []) Return a driver "Memcache" instance
39
 * @method static ExtendedCacheItemPoolInterface Memcached() Memcached($config = []) Return a driver "Memcached" instance
40
 * @method static ExtendedCacheItemPoolInterface Memstatic() Memstatic($config = []) Return a driver "Memstatic" instance
41
 * @method static ExtendedCacheItemPoolInterface Mongodb() Mongodb($config = []) Return a driver "Mongodb" instance
42
 * @method static ExtendedCacheItemPoolInterface Predis() Predis($config = []) Return a driver "Predis" instance
43
 * @method static ExtendedCacheItemPoolInterface Redis() Redis($config = []) Return a driver "Pedis" instance
44
 * @method static ExtendedCacheItemPoolInterface Riak() Riak($config = []) Return a driver "Riak" instance
45
 * @method static ExtendedCacheItemPoolInterface Sqlite() Sqlite($config = []) Return a driver "Sqlite" instance
46
 * @method static ExtendedCacheItemPoolInterface Ssdb() Ssdb($config = []) Return a driver "Ssdb" instance
47
 * @method static ExtendedCacheItemPoolInterface Wincache() Wincache($config = []) Return a driver "Wincache" instance
48
 * @method static ExtendedCacheItemPoolInterface Xcache() Xcache($config = []) Return a driver "Xcache" instance
49
 * @method static ExtendedCacheItemPoolInterface Zenddisk() Zenddisk($config = []) Return a driver "Zend disk cache" instance
50
 * @method static ExtendedCacheItemPoolInterface Zendshm() Zendshm($config = []) Return a driver "Zend memory cache" instance
51
 *
52
 */
53
class CacheManager
54
{
55
    const AUTOMATIC_DRIVER_CLASS = 'Auto';
56
    const CORE_DRIVER_NAMESPACE = 'Phpfastcache\Drivers\\';
57
58
    use ClassNamespaceResolverTrait;
59
60
    /**
61
     * @var ConfigurationOption
62
     */
63
    protected static $config;
64
65
    /**
66
     * @var int
67
     */
68
    public static $ReadHits = 0;
69
70
    /**
71
     * @var int
72
     */
73
    public static $WriteHits = 0;
74
75
    /**
76
     * @var string
77
     */
78
    protected static $namespacePath;
79
80
    /**
81
     * @var ExtendedCacheItemPoolInterface[]
82
     */
83
    protected static $instances = [];
84
85
    /**
86
     * @var array
87
     */
88
    protected static $driverOverrides = [];
89
90
    /**
91
     * @var array
92
     */
93
    protected static $driverCustoms = [];
94
95
    /**
96
     * @var array
97
     */
98
    protected static $badPracticeOmeter = [];
99
100
    /**
101
     * @param string $driver
102
     * @param array|ConfigurationOption $config
103
     * @param string $instanceId
104
     *
105
     * @return ExtendedCacheItemPoolInterface
106
     *
107
     * @throws PhpfastcacheDriverCheckException
108
     * @throws PhpfastcacheInvalidConfigurationException
109
     * @throws PhpfastcacheDriverNotFoundException
110
     * @throws PhpfastcacheInvalidArgumentException
111
     * @throws PhpfastcacheDriverException
112
     */
113
    public static function getInstance(string $driver = self::AUTOMATIC_DRIVER_CLASS, $config = null, string $instanceId = null): ExtendedCacheItemPoolInterface
114
    {
115
        $config = self::validateConfig($config);
116
        $driver = self::standardizeDriverName($driver);
117
118
        if (!$driver || $driver === self::AUTOMATIC_DRIVER_CLASS) {
119
            $driver = self::getAutoClass($config);
120
        }
121
122
        $instanceId = $instanceId ?: \md5($driver . \serialize($config->toArray()));
123
124
        if (!isset(self::$instances[$instanceId])) {
125
            self::$badPracticeOmeter[$driver] = 1;
126
            $driverClass = self::validateDriverClass(self::getDriverClass($driver));
127
128
            try {
129
                if (\class_exists($driverClass)) {
130
                    $configClass = $driverClass::getConfigClass();
131
                    self::$instances[$instanceId] = new $driverClass(new $configClass($config->toArray()), $instanceId);
132
                    self::$instances[$instanceId]->setEventManager(EventManager::getInstance());
133
                } else {
134
                    throw new PhpfastcacheDriverNotFoundException(\sprintf('The driver "%s" does not exists', $driver));
135
                }
136
            } catch (PhpfastcacheDriverCheckException $e) {
137
                return self::getFallbackInstance($driver, $config, $e);
138
            }
139
        } else {
140
            if (self::$badPracticeOmeter[$driver] >= 2) {
141
                \trigger_error('[' . $driver . '] Calling many times CacheManager::getInstance() for already instanced drivers is a bad practice and have a significant impact on performances.
142
           See https://github.com/PHPSocialNetwork/phpfastcache/wiki/[V5]-Why-calling-getInstance%28%29-each-time-is-a-bad-practice-%3F');
143
            }
144
        }
145
146
        self::$badPracticeOmeter[$driver]++;
147
148
        return self::$instances[$instanceId];
149
    }
150
151
    /**
152
     * @param string $driver
153
     * @param ConfigurationOption $config
154
     * @param PhpfastcacheDriverCheckException $e
155
     * @return ExtendedCacheItemPoolInterface
156
     * @throws PhpfastcacheDriverCheckException
157
     * @throws PhpfastcacheDriverException
158
     * @throws PhpfastcacheDriverNotFoundException
159
     * @throws PhpfastcacheInvalidConfigurationException
160
     */
161
    protected static function getFallbackInstance(string $driver, ConfigurationOption $config, PhpfastcacheDriverCheckException $e)
162
    {
163
        if ($config->getFallback()) {
164
            try {
165
                $fallback = $config->getFallback();
166
                $config->setFallback('');
167
                \trigger_error(\sprintf('The "%s" driver is unavailable at the moment, the fallback driver "%s" has been used instead.', $driver,
168
                    $fallback),  \E_USER_WARNING);
169
                return self::getInstance($fallback, $config->getFallbackConfig());
170
            } catch (PhpfastcacheInvalidArgumentException $e) {
171
                throw new PhpfastcacheInvalidConfigurationException('Invalid fallback driver configuration', 0, $e);
172
            }
173
        } else {
174
            throw new PhpfastcacheDriverCheckException($e->getMessage(), $e->getCode(), $e);
175
        }
176
    }
177
178
    /**
179
     * @param string $instanceId
180
     *
181
     * @return ExtendedCacheItemPoolInterface
182
     *
183
     * @throws PhpfastcacheInvalidArgumentException
184
     * @throws PhpfastcacheInstanceNotFoundException
185
     */
186
    public static function getInstanceById(string $instanceId): ExtendedCacheItemPoolInterface
187
    {
188
        if (isset(self::$instances[$instanceId])) {
189
            return self::$instances[$instanceId];
190
        }
191
192
        throw new PhpfastcacheInstanceNotFoundException(\sprintf('Instance ID %s not found', $instanceId));
193
    }
194
195
    /**
196
     * Return the list of instances
197
     *
198
     * @return ExtendedCacheItemPoolInterface[]
199
     */
200
    public static function getInstances(): array
201
    {
202
        return self::$instances;
203
    }
204
205
    /**
206
     * This method is intended for internal
207
     * use only and should not be used for
208
     * any external development use the
209
     * getInstances() method instead
210
     *
211
     * @todo Use a proper way to passe them as a reference ?
212
     * @internal
213
     * @return ExtendedCacheItemPoolInterface[]
214
     */
215
    public static function &getInternalInstances(): array
216
    {
217
        return self::$instances;
218
    }
219
220
    /**
221
     * @param ConfigurationOption $config
222
     * @return string
223
     * @throws PhpfastcacheDriverCheckException
224
     * @throws \Phpfastcache\Exceptions\PhpfastcacheLogicException
225
     */
226
    public static function getAutoClass(ConfigurationOption $config): string
227
    {
228
        static $autoDriver;
229
230
        if ($autoDriver === null) {
231
            foreach (self::getDriverList() as $driver) {
232
                /** @var ExtendedCacheItemPoolInterface $driverClass */
233
                $driverClass = self::CORE_DRIVER_NAMESPACE . $driver . '\Driver';
234
                if ($driverClass::isUsableInAutoContext()) {
235
                    try {
236
                        self::getInstance($driver, $config);
237
                        $autoDriver = $driver;
238
                        break;
239
                    } catch (PhpfastcacheDriverCheckException $e) {
240
                        continue;
241
                    }
242
                }
243
            }
244
        }
245
246
        if (!$autoDriver || !\is_string($autoDriver)) {
247
            throw new PhpfastcacheLogicException('Unable to find out a valid driver automatically');
248
        }
249
250
        self::$badPracticeOmeter[$autoDriver]--;
251
252
        return $autoDriver;
253
    }
254
255
    /**
256
     * @param string $name
257
     * @param array $arguments
258
     * @return ExtendedCacheItemPoolInterface
259
     */
260
    public static function __callStatic(string $name, array $arguments): ExtendedCacheItemPoolInterface
261
    {
262
        $options = (\array_key_exists(0, $arguments) && \is_array($arguments) ? $arguments[0] : []);
263
264
        return self::getInstance($name, $options);
265
    }
266
267
    /**
268
     * @return bool
269
     */
270
    public static function clearInstances(): bool
271
    {
272
        self::$instances = [];
273
274
        \gc_collect_cycles();
275
        return !\count(self::$instances);
276
    }
277
278
    /**
279
     * @param ExtendedCacheItemPoolInterface $cachePoolInstance
280
     * @return bool
281
     * @since 7.0.4
282
     */
283
    public static function clearInstance(ExtendedCacheItemPoolInterface $cachePoolInstance): bool
284
    {
285
        $found = false;
286
        self::$instances = \array_filter(\array_map(function (ExtendedCacheItemPoolInterface $cachePool) use ($cachePoolInstance, &$found){
287
            if(\spl_object_hash($cachePool) === \spl_object_hash($cachePoolInstance)){
288
                $found = true;
289
                return null;
290
            }
291
            return $cachePool;
292
        }, self::$instances));
293
294
        return $found;
295
    }
296
297
    /**
298
     * @return string
299
     */
300
    public static function getNamespacePath(): string
301
    {
302
        return self::$namespacePath ?: self::getDefaultNamespacePath();
303
    }
304
305
    /**
306
     * @return string
307
     */
308
    public static function getDefaultNamespacePath(): string
309
    {
310
        return self::CORE_DRIVER_NAMESPACE;
311
    }
312
313
    /**
314
     * @param string $path
315
     * @deprecated This method has been deprecated as of V7, please use driver override feature instead
316
     */
317
    public static function setNamespacePath($path)
318
    {
319
        \trigger_error('This method has been deprecated as of V7, please use cache manager "override" or "custom driver" features instead', E_USER_DEPRECATED);
320
        self::$namespacePath = \trim($path, "\\") . '\\';
321
    }
322
323
    /**
324
     * @param ConfigurationOption $config
325
     */
326
    public static function setDefaultConfig(ConfigurationOption $config)
327
    {
328
        self::$config = $config;
329
    }
330
331
    /**
332
     * @return ConfigurationOption
333
     */
334
    public static function getDefaultConfig(): ConfigurationOption
335
    {
336
        return self::$config ?: self::$config = new ConfigurationOption();
337
    }
338
339
    /**
340
     * @return array
341
     * @deprecated As of V7 will be removed soon or later, use CacheManager::getDriverList() instead
342
     */
343
    public static function getStaticSystemDrivers(): array
344
    {
345
        \trigger_error(\sprintf('Method "%s" is deprecated as of the V7 and will be removed soon or later, use CacheManager::getDriverList() instead.',
346
            __METHOD__), \E_USER_DEPRECATED);
347
        return [
348
            'Apc',
349
            'Apcu',
350
            'Cassandra',
351
            'Couchbase',
352
            'Couchdb',
353
            'Devnull',
354
            'Files',
355
            'Leveldb',
356
            'Memcache',
357
            'Memcached',
358
            'Memstatic',
359
            'Mongodb',
360
            'Predis',
361
            'Redis',
362
            'Riak',
363
            'Ssdb',
364
            'Sqlite',
365
            'Wincache',
366
            'Xcache',
367
            'Zenddisk',
368
            'Zendshm',
369
        ];
370
    }
371
372
    /**
373
     * @return array
374
     * @deprecated As of V7 will be removed soon or later, use CacheManager::getDriverList() instead
375
     */
376
    public static function getStaticAllDrivers(): array
377
    {
378
        \trigger_error(\sprintf('Method "%s" is deprecated as of the V7 and will be removed soon or later, use CacheManager::getDriverList() instead.',
379
            __METHOD__), \E_USER_DEPRECATED);
380
        return \array_merge(self::getStaticSystemDrivers(), [
381
            'Devtrue',
382
            'Devfalse',
383
            'Cookie',
384
        ]);
385
    }
386
387
    /**
388
     * Return the list of available drivers Capitalized
389
     * with optional FQCN as key
390
     *
391
     * @param bool $FQCNAsKey Describe keys with Full Qualified Class Name
392
     * @return string[]
393
     * @throws PhpfastcacheUnsupportedOperationException
394
     */
395
    public static function getDriverList(bool $FQCNAsKey = false): array
396
    {
397
        static $driverList;
398
399
        if (self::getDefaultNamespacePath() === self::getNamespacePath()) {
400
            if ($driverList === null) {
401
                $prefix = self::CORE_DRIVER_NAMESPACE;
402
                $classMap = self::createClassMap(__DIR__ . '/Drivers');
403
                $driverList = [];
404
405
                foreach ($classMap as $class => $file) {
406
                    $driverList[] = \str_replace($prefix, '', \substr($class, 0, \strrpos($class, '\\')));
407
                }
408
409
                $driverList = \array_values(\array_unique($driverList));
410
            }
411
412
            $driverList = \array_merge($driverList, \array_keys(self::$driverCustoms));
413
414
            if ($FQCNAsKey) {
415
                $realDriverList = [];
416
                foreach ($driverList as $driverName) {
417
                    $realDriverList[self::getDriverClass($driverName)] = $driverName;
418
                }
419
                $driverList = $realDriverList;
420
            }
421
422
            \asort($driverList);
423
424
            return $driverList;
425
        }
426
427
        throw new PhpfastcacheUnsupportedOperationException('Cannot get the driver list if the default namespace path has changed.');
428
    }
429
430
    /**
431
     * @param string $driverName
432
     * @return string
433
     */
434
    public static function standardizeDriverName(string $driverName): string
435
    {
436
        return \ucfirst(\strtolower(\trim($driverName)));
437
    }
438
439
    /**
440
     * @param string $driverName
441
     * @return string
442
     */
443
    public static function getDriverClass(string $driverName): string
444
    {
445
        if (!empty(self::$driverCustoms[$driverName])) {
446
            $driverClass = self::$driverCustoms[$driverName];
447
        } else {
448
            if (!empty(self::$driverOverrides[$driverName])) {
449
                $driverClass = self::$driverOverrides[$driverName];
450
            } else {
451
                $driverClass = self::getNamespacePath() . $driverName . '\Driver';
452
            }
453
        }
454
455
        return $driverClass;
456
    }
457
458
    /**
459
     * @param string $driverName
460
     * @param string $className
461
     * @throws \Phpfastcache\Exceptions\PhpfastcacheInvalidArgumentException
462
     * @throws \Phpfastcache\Exceptions\PhpfastcacheLogicException
463
     * @throws \Phpfastcache\Exceptions\PhpfastcacheUnsupportedOperationException
464
     * @return void
465
     */
466
    public static function addCustomDriver(string $driverName, string $className)
467
    {
468
        $driverName = self::standardizeDriverName($driverName);
469
470
        if (empty($driverName)) {
471
            throw new PhpfastcacheInvalidArgumentException("Can't add a custom driver because its name is empty");
472
        }
473
474
        if (!\class_exists($className)) {
475
            throw new PhpfastcacheInvalidArgumentException(
476
                \sprintf("Can't add '%s' because the class '%s' does not exists", $driverName, $className)
477
            );
478
        }
479
480
        if (!empty(self::$driverCustoms[$driverName])) {
481
            throw new PhpfastcacheLogicException(\sprintf("Driver '%s' has been already added", $driverName));
482
        }
483
484
        if (\in_array($driverName, self::getDriverList(), true)) {
485
            throw new PhpfastcacheLogicException(\sprintf("Driver '%s' is already a part of the PhpFastCache core", $driverName));
486
        }
487
488
        self::$driverCustoms[$driverName] = $className;
489
    }
490
491
    /**
492
     * @param string $driverName
493
     * @throws \Phpfastcache\Exceptions\PhpfastcacheInvalidArgumentException
494
     * @throws \Phpfastcache\Exceptions\PhpfastcacheLogicException
495
     * @return void
496
     */
497
    public static function removeCustomDriver(string $driverName)
498
    {
499
        $driverName = self::standardizeDriverName($driverName);
500
501
        if (empty($driverName)) {
502
            throw new PhpfastcacheInvalidArgumentException("Can't remove a custom driver because its name is empty");
503
        }
504
505
        if (!isset(self::$driverCustoms[$driverName])) {
506
            throw new PhpfastcacheLogicException(\sprintf("Driver '%s' does not exists", $driverName));
507
        }
508
509
        unset(self::$driverCustoms[$driverName]);
510
    }
511
512
    /**
513
     * @param string $driverName
514
     * @param string $className
515
     * @throws \Phpfastcache\Exceptions\PhpfastcacheInvalidArgumentException
516
     * @throws \Phpfastcache\Exceptions\PhpfastcacheLogicException
517
     * @throws \Phpfastcache\Exceptions\PhpfastcacheUnsupportedOperationException
518
     * @return void
519
     */
520
    public static function addCoreDriverOverride(string $driverName, string $className)
521
    {
522
        $driverName = self::standardizeDriverName($driverName);
523
524
        if (empty($driverName)) {
525
            throw new PhpfastcacheInvalidArgumentException("Can't add a core driver override because its name is empty");
526
        }
527
528
        if (!\class_exists($className)) {
529
            throw new PhpfastcacheInvalidArgumentException(
530
                \sprintf("Can't override '%s' because the class '%s' does not exists", $driverName, $className)
531
            );
532
        }
533
534
        if (!empty(self::$driverOverrides[$driverName])) {
535
            throw new PhpfastcacheLogicException(\sprintf("Driver '%s' has been already overridden", $driverName));
536
        }
537
538
        if (!\in_array($driverName, self::getDriverList(), true)) {
539
            throw new PhpfastcacheLogicException(\sprintf("Driver '%s' can't be overridden since its not a part of the PhpFastCache core", $driverName));
540
        }
541
542
        if (!\is_subclass_of($className, self::CORE_DRIVER_NAMESPACE . $driverName . '\\Driver', true)) {
543
            throw new PhpfastcacheLogicException(
544
                \sprintf("Can't override '%s' because the class '%s' MUST extend '%s'", $driverName, $className,
545
                    self::CORE_DRIVER_NAMESPACE . $driverName . '\\Driver')
546
            );
547
        }
548
549
        self::$driverOverrides[$driverName] = $className;
550
    }
551
552
    /**
553
     * @param string $driverName
554
     * @throws \Phpfastcache\Exceptions\PhpfastcacheInvalidArgumentException
555
     * @throws \Phpfastcache\Exceptions\PhpfastcacheLogicException
556
     * @return void
557
     */
558
    public static function removeCoreDriverOverride(string $driverName)
559
    {
560
        $driverName = self::standardizeDriverName($driverName);
561
562
        if (empty($driverName)) {
563
            throw new PhpfastcacheInvalidArgumentException("Can't remove a core driver override because its name is empty");
564
        }
565
566
        if (!isset(self::$driverOverrides[$driverName])) {
567
            throw new PhpfastcacheLogicException(\sprintf("Driver '%s' were not overridden", $driverName));
568
        }
569
570
        unset(self::$driverOverrides[$driverName]);
571
    }
572
573
    /**
574
     * @param array|ConfigurationOption
575
     * @return ConfigurationOption
576
     * @throws PhpfastcacheInvalidArgumentException
577
     * @throws PhpfastcacheInvalidConfigurationException
578
     */
579
    protected static function validateConfig($config): ConfigurationOption
580
    {
581
        if (\is_array($config)) {
582
            $config = new ConfigurationOption($config);
583
            \trigger_error(
584
                'The CacheManager will drops the support of primitive configuration arrays, use a "\Phpfastcache\Config\ConfigurationOption" object instead',
585
                E_USER_DEPRECATED
586
            );
587
        } elseif ($config === null) {
588
            $config = self::getDefaultConfig();
589
        } else {
590
            if (!($config instanceof ConfigurationOption)) {
591
                throw new PhpfastcacheInvalidArgumentException(\sprintf('Unsupported config type: %s', \gettype($config)));
592
            }
593
        }
594
595
        return $config;
596
    }
597
598
    /**
599
     * @param string $driverClass
600
     * @return string
601
     * @throws PhpfastcacheDriverException
602
     */
603
    protected static function validateDriverClass(string $driverClass): string
604
    {
605
        if (!\is_a($driverClass, ExtendedCacheItemPoolInterface::class, true)) {
606
            throw new PhpfastcacheDriverException(\sprintf(
607
                'Class "%s" does not implement "%s"',
608
                $driverClass,
609
                ExtendedCacheItemPoolInterface::class
610
            ));
611
        }
612
        return $driverClass;
613
    }
614
}
615