Completed
Push — master ( 4e7427...4a94bb )
by Georges
12s
created

CacheManager::getInstance()   B

Complexity

Conditions 8
Paths 16

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 16
nop 3
dl 0
loc 36
rs 8.0995
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
     * This method is intended for internal
197
     * use only and should not be used for
198
     * any external development use the
199
     * getInstances() method instead
200
     *
201
     * @internal
202
     * @return ExtendedCacheItemPoolInterface[]
203
     */
204
    public static function getInstances(): array
205
    {
206
        return self::$instances;
207
    }
208
209
    /**
210
     * This method is intended for internal
211
     * use only and should not be used for
212
     * any external development use the
213
     * getInstances() method instead
214
     *
215
     * @todo Use a proper way to passe them as a reference ?
216
     * @internal
217
     * @return ExtendedCacheItemPoolInterface[]
218
     */
219
    public static function &getInternalInstances(): array
220
    {
221
        return self::$instances;
222
    }
223
224
    /**
225
     * @param ConfigurationOption $config
226
     * @return string
227
     * @throws PhpfastcacheDriverCheckException
228
     * @throws \Phpfastcache\Exceptions\PhpfastcacheLogicException
229
     */
230
    public static function getAutoClass(ConfigurationOption $config): string
231
    {
232
        static $autoDriver;
233
234
        if ($autoDriver === null) {
235
            foreach (self::getDriverList() as $driver) {
236
                /** @var ExtendedCacheItemPoolInterface $driverClass */
237
                $driverClass = self::CORE_DRIVER_NAMESPACE . $driver . '\Driver';
238
                if ($driverClass::isUsableInAutoContext()) {
239
                    try {
240
                        self::getInstance($driver, $config);
241
                        $autoDriver = $driver;
242
                        break;
243
                    } catch (PhpfastcacheDriverCheckException $e) {
244
                        continue;
245
                    }
246
                }
247
            }
248
        }
249
250
        if (!$autoDriver || !\is_string($autoDriver)) {
251
            throw new PhpfastcacheLogicException('Unable to find out a valid driver automatically');
252
        }
253
254
        self::$badPracticeOmeter[$autoDriver]--;
255
256
        return $autoDriver;
257
    }
258
259
    /**
260
     * @param string $name
261
     * @param array $arguments
262
     * @return ExtendedCacheItemPoolInterface
263
     */
264
    public static function __callStatic(string $name, array $arguments): ExtendedCacheItemPoolInterface
265
    {
266
        $options = (\array_key_exists(0, $arguments) && \is_array($arguments) ? $arguments[0] : []);
267
268
        return self::getInstance($name, $options);
269
    }
270
271
    /**
272
     * @return bool
273
     */
274
    public static function clearInstances(): bool
275
    {
276
        self::$instances = [];
277
278
        \gc_collect_cycles();
279
        return !\count(self::$instances);
280
    }
281
282
    /**
283
     * @return string
284
     */
285
    public static function getNamespacePath(): string
286
    {
287
        return self::$namespacePath ?: self::getDefaultNamespacePath();
288
    }
289
290
    /**
291
     * @return string
292
     */
293
    public static function getDefaultNamespacePath(): string
294
    {
295
        return self::CORE_DRIVER_NAMESPACE;
296
    }
297
298
    /**
299
     * @param string $path
300
     * @deprecated This method has been deprecated as of V7, please use driver override feature instead
301
     */
302
    public static function setNamespacePath($path)
303
    {
304
        \trigger_error('This method has been deprecated as of V7, please use cache manager "override" or "custom driver" features instead', E_USER_DEPRECATED);
305
        self::$namespacePath = \trim($path, "\\") . '\\';
306
    }
307
308
    /**
309
     * @param ConfigurationOption $config
310
     */
311
    public static function setDefaultConfig(ConfigurationOption $config)
312
    {
313
        self::$config = $config;
314
    }
315
316
    /**
317
     * @return ConfigurationOption
318
     */
319
    public static function getDefaultConfig(): ConfigurationOption
320
    {
321
        return self::$config ?: self::$config = new ConfigurationOption();
322
    }
323
324
    /**
325
     * @return array
326
     * @deprecated As of V7 will be removed soon or later, use CacheManager::getDriverList() instead
327
     */
328
    public static function getStaticSystemDrivers(): array
329
    {
330
        \trigger_error(\sprintf('Method "%s" is deprecated as of the V7 and will be removed soon or later, use CacheManager::getDriverList() instead.',
331
            __METHOD__), E_USER_DEPRECATED);
332
        return [
333
            'Apc',
334
            'Apcu',
335
            'Cassandra',
336
            'Couchbase',
337
            'Couchdb',
338
            'Devnull',
339
            'Files',
340
            'Leveldb',
341
            'Memcache',
342
            'Memcached',
343
            'Memstatic',
344
            'Mongodb',
345
            'Predis',
346
            'Redis',
347
            'Riak',
348
            'Ssdb',
349
            'Sqlite',
350
            'Wincache',
351
            'Xcache',
352
            'Zenddisk',
353
            'Zendshm',
354
        ];
355
    }
356
357
    /**
358
     * @return array
359
     * @deprecated As of V7 will be removed soon or later, use CacheManager::getDriverList() instead
360
     */
361
    public static function getStaticAllDrivers(): array
362
    {
363
        \trigger_error(\sprintf('Method "%s" is deprecated as of the V7 and will be removed soon or later, use CacheManager::getDriverList() instead.',
364
            __METHOD__), E_USER_DEPRECATED);
365
        return \array_merge(self::getStaticSystemDrivers(), [
366
            'Devtrue',
367
            'Devfalse',
368
            'Cookie',
369
        ]);
370
    }
371
372
    /**
373
     * @param bool $FQCNAsKey Describe keys with Full Qualified Class Name
374
     * @return string[]
375
     * @throws PhpfastcacheUnsupportedOperationException
376
     */
377
    public static function getDriverList(bool $FQCNAsKey = false): array
378
    {
379
        static $driverList;
380
381
        if (self::getDefaultNamespacePath() === self::getNamespacePath()) {
382
            if ($driverList === null) {
383
                $prefix = self::CORE_DRIVER_NAMESPACE;
384
                $classMap = self::createClassMap(__DIR__ . '/Drivers');
385
                $driverList = [];
386
387
                foreach ($classMap as $class => $file) {
388
                    $driverList[] = \str_replace($prefix, '', \substr($class, 0, \strrpos($class, '\\')));
389
                }
390
391
                $driverList = \array_values(\array_unique($driverList));
392
            }
393
394
            $driverList = \array_merge($driverList, \array_keys(self::$driverCustoms));
395
396
            if ($FQCNAsKey) {
397
                $realDriverList = [];
398
                foreach ($driverList as $driverName) {
399
                    $realDriverList[self::getDriverClass($driverName)] = $driverName;
400
                }
401
                $driverList = $realDriverList;
402
            }
403
404
            \asort($driverList);
405
406
            return $driverList;
407
        }
408
409
        throw new PhpfastcacheUnsupportedOperationException('Cannot get the driver list if the default namespace path has changed.');
410
    }
411
412
    /**
413
     * @param string $driverName
414
     * @return string
415
     */
416
    public static function standardizeDriverName(string $driverName): string
417
    {
418
        return \ucfirst(\strtolower(\trim($driverName)));
419
    }
420
421
    /**
422
     * @param string $driverName
423
     * @return string
424
     */
425
    public static function getDriverClass(string $driverName): string
426
    {
427
        if (!empty(self::$driverCustoms[$driverName])) {
428
            $driverClass = self::$driverCustoms[$driverName];
429
        } else {
430
            if (!empty(self::$driverOverrides[$driverName])) {
431
                $driverClass = self::$driverOverrides[$driverName];
432
            } else {
433
                $driverClass = self::getNamespacePath() . $driverName . '\Driver';
434
            }
435
        }
436
437
        return $driverClass;
438
    }
439
440
    /**
441
     * @param string $driverName
442
     * @param string $className
443
     * @throws \Phpfastcache\Exceptions\PhpfastcacheInvalidArgumentException
444
     * @throws \Phpfastcache\Exceptions\PhpfastcacheLogicException
445
     * @throws \Phpfastcache\Exceptions\PhpfastcacheUnsupportedOperationException
446
     * @return void
447
     */
448
    public static function addCustomDriver(string $driverName, string $className)
449
    {
450
        $driverName = self::standardizeDriverName($driverName);
451
452
        if (empty($driverName)) {
453
            throw new PhpfastcacheInvalidArgumentException("Can't add a custom driver because its name is empty");
454
        }
455
456
        if (!\class_exists($className)) {
457
            throw new PhpfastcacheInvalidArgumentException(
458
                \sprintf("Can't add '%s' because the class '%s' does not exists", $driverName, $className)
459
            );
460
        }
461
462
        if (!empty(self::$driverCustoms[$driverName])) {
463
            throw new PhpfastcacheLogicException(\sprintf("Driver '%s' has been already added", $driverName));
464
        }
465
466
        if (\in_array($driverName, self::getDriverList(), true)) {
467
            throw new PhpfastcacheLogicException(\sprintf("Driver '%s' is already a part of the PhpFastCache core", $driverName));
468
        }
469
470
        self::$driverCustoms[$driverName] = $className;
471
    }
472
473
    /**
474
     * @param string $driverName
475
     * @throws \Phpfastcache\Exceptions\PhpfastcacheInvalidArgumentException
476
     * @throws \Phpfastcache\Exceptions\PhpfastcacheLogicException
477
     * @return void
478
     */
479
    public static function removeCustomDriver(string $driverName)
480
    {
481
        $driverName = self::standardizeDriverName($driverName);
482
483
        if (empty($driverName)) {
484
            throw new PhpfastcacheInvalidArgumentException("Can't remove a custom driver because its name is empty");
485
        }
486
487
        if (!isset(self::$driverCustoms[$driverName])) {
488
            throw new PhpfastcacheLogicException(\sprintf("Driver '%s' does not exists", $driverName));
489
        }
490
491
        unset(self::$driverCustoms[$driverName]);
492
    }
493
494
    /**
495
     * @param string $driverName
496
     * @param string $className
497
     * @throws \Phpfastcache\Exceptions\PhpfastcacheInvalidArgumentException
498
     * @throws \Phpfastcache\Exceptions\PhpfastcacheLogicException
499
     * @throws \Phpfastcache\Exceptions\PhpfastcacheUnsupportedOperationException
500
     * @return void
501
     */
502
    public static function addCoreDriverOverride(string $driverName, string $className)
503
    {
504
        $driverName = self::standardizeDriverName($driverName);
505
506
        if (empty($driverName)) {
507
            throw new PhpfastcacheInvalidArgumentException("Can't add a core driver override because its name is empty");
508
        }
509
510
        if (!\class_exists($className)) {
511
            throw new PhpfastcacheInvalidArgumentException(
512
                \sprintf("Can't override '%s' because the class '%s' does not exists", $driverName, $className)
513
            );
514
        }
515
516
        if (!empty(self::$driverOverrides[$driverName])) {
517
            throw new PhpfastcacheLogicException(\sprintf("Driver '%s' has been already overridden", $driverName));
518
        }
519
520
        if (!\in_array($driverName, self::getDriverList(), true)) {
521
            throw new PhpfastcacheLogicException(\sprintf("Driver '%s' can't be overridden since its not a part of the PhpFastCache core", $driverName));
522
        }
523
524
        if (!\is_subclass_of($className, self::CORE_DRIVER_NAMESPACE . $driverName . '\\Driver', true)) {
525
            throw new PhpfastcacheLogicException(
526
                \sprintf("Can't override '%s' because the class '%s' MUST extend '%s'", $driverName, $className,
527
                    self::CORE_DRIVER_NAMESPACE . $driverName . '\\Driver')
528
            );
529
        }
530
531
        self::$driverOverrides[$driverName] = $className;
532
    }
533
534
    /**
535
     * @param string $driverName
536
     * @throws \Phpfastcache\Exceptions\PhpfastcacheInvalidArgumentException
537
     * @throws \Phpfastcache\Exceptions\PhpfastcacheLogicException
538
     * @return void
539
     */
540
    public static function removeCoreDriverOverride(string $driverName)
541
    {
542
        $driverName = self::standardizeDriverName($driverName);
543
544
        if (empty($driverName)) {
545
            throw new PhpfastcacheInvalidArgumentException("Can't remove a core driver override because its name is empty");
546
        }
547
548
        if (!isset(self::$driverOverrides[$driverName])) {
549
            throw new PhpfastcacheLogicException(\sprintf("Driver '%s' were not overridden", $driverName));
550
        }
551
552
        unset(self::$driverOverrides[$driverName]);
553
    }
554
555
    /**
556
     * @param array|ConfigurationOption
557
     * @return ConfigurationOption
558
     * @throws PhpfastcacheInvalidArgumentException
559
     * @throws PhpfastcacheInvalidConfigurationException
560
     */
561
    protected static function validateConfig($config): ConfigurationOption
562
    {
563
        if (\is_array($config)) {
564
            $config = new ConfigurationOption($config);
565
            \trigger_error(
566
                'The CacheManager will drops the support of primitive configuration arrays, use a "\Phpfastcache\Config\ConfigurationOption" object instead',
567
                E_USER_DEPRECATED
568
            );
569
        } elseif ($config === null) {
570
            $config = self::getDefaultConfig();
571
        } else {
572
            if (!($config instanceof ConfigurationOption)) {
573
                throw new PhpfastcacheInvalidArgumentException(\sprintf('Unsupported config type: %s', \gettype($config)));
574
            }
575
        }
576
577
        return $config;
578
    }
579
580
    /**
581
     * @param string $driverClass
582
     * @return string
583
     * @throws PhpfastcacheDriverException
584
     */
585
    protected static function validateDriverClass(string $driverClass): string
586
    {
587
        if (!\is_a($driverClass, ExtendedCacheItemPoolInterface::class, true)) {
588
            throw new PhpfastcacheDriverException(\sprintf(
589
                'Class "%s" does not implement "%s"',
590
                $driverClass,
591
                ExtendedCacheItemPoolInterface::class
592
            ));
593
        }
594
        return $driverClass;
595
    }
596
}
597