Completed
Push — master ( 730f80...c057de )
by Lars
02:24
created

Cache::__construct()   C

Complexity

Conditions 13
Paths 36

Size

Total Lines 76

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 13.1868

Importance

Changes 0
Metric Value
dl 0
loc 76
ccs 26
cts 29
cp 0.8966
rs 5.8169
c 0
b 0
f 0
cc 13
nc 36
nop 11
crap 13.1868

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace voku\cache;
6
7
use voku\cache\Exception\InvalidArgumentException;
8
9
/**
10
 * Cache: global-cache class
11
 *
12
 * can use different cache-adapter:
13
 * - Redis
14
 * - Memcache / Memcached
15
 * - APC / APCu
16
 * - Xcache
17
 * - Array
18
 * - File / OpCache
19
 */
20
class Cache implements iCache
21
{
22
    /**
23
     * @var array
24
     */
25
    protected static $STATIC_CACHE = [];
26
27
    /**
28
     * @var array
29
     */
30
    protected static $STATIC_CACHE_EXPIRE = [];
31
32
    /**
33
     * @var array
34
     */
35
    protected static $STATIC_CACHE_COUNTER = [];
36
37
    /**
38
     * @var iAdapter|null
39
     */
40
    protected $adapter;
41
42
    /**
43
     * @var iSerializer|null
44
     */
45
    protected $serializer;
46
47
    /**
48
     * @var array
49
     */
50
    protected $unserialize_options = ['allowed_classes' => true];
51
52
    /**
53
     * @var string
54
     */
55
    protected $prefix = '';
56
57
    /**
58
     * @var bool
59
     */
60
    protected $isReady = false;
61
62
    /**
63
     * @var bool
64
     */
65
    protected $isActive = true;
66
67
    /**
68
     * @var bool
69
     */
70
    protected $useCheckForDev;
71
72
    /**
73
     * @var bool
74
     */
75
    protected $useCheckForAdminSession;
76
77
    /**
78
     * @var bool
79
     */
80
    protected $useCheckForServerIpIsClientIp;
81
82
    /**
83
     * @var string
84
     */
85
    protected $disableCacheGetParameter;
86
87
    /**
88
     * @var bool
89
     */
90
    protected $isAdminSession;
91
92
    /**
93
     * @var int
94
     */
95
    protected $staticCacheHitCounter = 10;
96
97
    /**
98
     * __construct
99
     *
100
     * @param iAdapter|null           $adapter
101
     * @param iSerializer|null        $serializer
102
     * @param bool                    $checkForUsage                              <p>check for admin-session && check
103
     *                                                                            for server-ip == client-ip
104
     *                                                                            && check for dev</p>
105
     * @param bool                    $cacheEnabled                               <p>false === disable the cache (use
106
     *                                                                            it
107
     *                                                                            e.g. for global settings)</p>
108
     * @param bool                    $isAdminSession                             <p>true === disable cache for this
109
     *                                                                            user
110
     *                                                                            (use it e.g. for admin user settings)
111
     * @param bool                    $useCheckForAdminSession                    <p>use $isAdminSession flag or
112
     *                                                                            not</p>
113
     * @param bool                    $useCheckForDev                             <p>use checkForDev() or not</p>
114
     * @param bool                    $useCheckForServerIpIsClientIp              <p>use check for server-ip ==
115
     *                                                                            client-ip or not</p>
116
     * @param string                  $disableCacheGetParameter                   <p>set the _GET parameter for
117
     *                                                                            disabling the cache, disable this
118
     *                                                                            check via empty string</p>
119
     * @param CacheAdapterAutoManager $cacheAdapterManagerForAutoConnect          <p>Overwrite some Adapters for the
120
     *                                                                            auto-connect-function.</p>
121
     * @param bool                    $cacheAdapterManagerForAutoConnectOverwrite <p>true === Use only Adapters from
122
     *                                                                            your
123
     *                                                                            "CacheAdapterManager".</p>
124
     */
125 119
    public function __construct(
126
        iAdapter $adapter = null,
127
        iSerializer $serializer = null,
128
        bool $checkForUsage = true,
129
        bool $cacheEnabled = true,
130
        bool $isAdminSession = false,
131
        bool $useCheckForDev = true,
132
        bool $useCheckForAdminSession = true,
133
        bool $useCheckForServerIpIsClientIp = true,
134
        string $disableCacheGetParameter = 'testWithoutCache',
135
        CacheAdapterAutoManager $cacheAdapterManagerForAutoConnect = null,
136
        bool $cacheAdapterManagerForAutoConnectOverwrite = false
137
    ) {
138 119
        $this->isAdminSession = $isAdminSession;
139
140 119
        $this->useCheckForDev = $useCheckForDev;
141 119
        $this->useCheckForAdminSession = $useCheckForAdminSession;
142 119
        $this->useCheckForServerIpIsClientIp = $useCheckForServerIpIsClientIp;
143
144 119
        $this->disableCacheGetParameter = $disableCacheGetParameter;
145
146
        // First check if the cache is active at all.
147 119
        $this->isActive = $cacheEnabled;
148
        if (
149 119
            $this->isActive
150
            &&
151 119
            $checkForUsage
152
        ) {
153
            $this->setActive($this->isCacheActiveForTheCurrentUser());
154
        }
155
156
        // If the cache is active, then try to auto-connect to the best possible cache-system.
157 119
        if ($this->isActive) {
158 119
            $this->setPrefix($this->getTheDefaultPrefix());
159
160 119
            if ($adapter === null) {
161 27
                $adapter = $this->autoConnectToAvailableCacheSystem($cacheAdapterManagerForAutoConnect, $cacheAdapterManagerForAutoConnectOverwrite);
162
            }
163
164 119
            if (!\is_object($serializer) && $serializer === null) {
165
                if (
166 27
                    $adapter instanceof AdapterMemcached
167
                    ||
168 27
                    $adapter instanceof AdapterMemcache
169
                ) {
170
                    // INFO: Memcache(d) has his own "serializer", so don't use it twice
171
                    $serializer = new SerializerNo();
172
                } elseif (
173 27
                    $adapter instanceof AdapterOpCache
174
                    &&
175 27
                    \class_exists('\Symfony\Component\VarExporter\VarExporter')
176
                ) {
177
                    // INFO: opcache + Symfony-VarExporter don't need any "serializer"
178
                    $serializer = new SerializerNo();
179
                } else {
180
                    // set default serializer
181 27
                    $serializer = new SerializerIgbinary();
182
                }
183
            }
184
        }
185
186
        // Final checks ...
187
        if (
188 119
            $serializer !== null
189
            &&
190 119
            $adapter !== null
191
        ) {
192 119
            $this->setCacheIsReady(true);
193
194 119
            $this->adapter = $adapter;
195
196 119
            $this->serializer = $serializer;
197
198 119
            $this->serializer->setUnserializeOptions($this->unserialize_options);
199
        }
200 119
    }
201
202
    /**
203
     * @param array $array
204
     *
205
     * @return void
206
     */
207
    public function setUnserializeOptions(array $array = [])
208
    {
209
        $this->unserialize_options = $array;
210
    }
211
212
    /**
213
     * Auto-connect to the available cache-system on the server.
214
     *
215
     * @param CacheAdapterAutoManager $cacheAdapterManagerForAutoConnect          <p>Overwrite some Adapters for the
216
     *                                                                            auto-connect-function.</p>
217
     * @param bool                    $cacheAdapterManagerForAutoConnectOverwrite <p>true === Use only Adapters from
218
     *                                                                            your
219
     *                                                                            "CacheAdapterManager".</p>
220
     *
221
     * @return iAdapter
222
     */
223 27
    protected function autoConnectToAvailableCacheSystem(
224
        CacheAdapterAutoManager $cacheAdapterManagerForAutoConnect = null,
225
        bool $cacheAdapterManagerForAutoConnectOverwrite = false
226
    ): iAdapter {
227 27
        static $AUTO_ADAPTER_STATIC_CACHE = null;
228
229
        if (
230 27
            \is_object($AUTO_ADAPTER_STATIC_CACHE)
231
            &&
232 27
            $AUTO_ADAPTER_STATIC_CACHE instanceof iAdapter
233
        ) {
234 26
            return $AUTO_ADAPTER_STATIC_CACHE;
235
        }
236
237
        // init
238 1
        $adapter = null;
239
240 1
        $cacheAdapterManagerDefault = CacheAdapterAutoManager::getDefaultsForAutoInit();
241
242 1
        if ($cacheAdapterManagerForAutoConnect !== null) {
243 1
            if ($cacheAdapterManagerForAutoConnectOverwrite) {
244 1
                $cacheAdapterManagerDefault = $cacheAdapterManagerForAutoConnect;
245
            } else {
246
                /** @noinspection PhpUnhandledExceptionInspection */
247
                $cacheAdapterManagerDefault->merge($cacheAdapterManagerForAutoConnect);
248
            }
249
        }
250
251 1
        foreach ($cacheAdapterManagerDefault->getAdapters() as $adapterTmp => $callableFunctionTmp) {
252
253
            /** @var iAdapter $adapterTest */
254 1
            if ($callableFunctionTmp !== null) {
255 1
                $adapterTest = new $adapterTmp($callableFunctionTmp);
256
            } else {
257
                $adapterTest = new $adapterTmp();
258
            }
259
260 1
            if ($adapterTest->installed()) {
261 1
                $adapter = $adapterTest;
262
263 1
                break;
264
            }
265
        }
266
267
        // save to static cache
268 1
        $AUTO_ADAPTER_STATIC_CACHE = $adapter;
269
270 1
        return $adapter;
271
    }
272
273
    /**
274
     * Calculate store-key (prefix + $rawKey).
275
     *
276
     * @param string $rawKey
277
     *
278
     * @return string
279
     */
280 99
    protected function calculateStoreKey(string $rawKey): string
281
    {
282 99
        $str = $this->getPrefix() . $rawKey;
283
284 99
        if ($this->adapter instanceof AdapterFileAbstract) {
285 57
            $str = $this->cleanStoreKey($str);
286
        }
287
288 99
        return $str;
289
    }
290
291
    /**
292
     * Check for local developer.
293
     *
294
     * @return bool
295
     */
296
    protected function checkForDev(): bool
297
    {
298
        $return = false;
299
300
        if (\function_exists('checkForDev')) {
301
            $return = checkForDev();
302
        } else {
303
304
            // for testing with dev-address
305
            $noDev = isset($_GET['noDev']) ? (int) $_GET['noDev'] : 0;
306
            $remoteAddr = $_SERVER['REMOTE_ADDR'] ?? 'NO_REMOTE_ADDR';
307
308
            if (
309
                $noDev !== 1
310
                &&
311
                (
312
                    $remoteAddr === '127.0.0.1'
313
                    ||
314
                    $remoteAddr === '::1'
315
                    ||
316
                    \PHP_SAPI === 'cli'
317
                )
318
            ) {
319
                $return = true;
320
            }
321
        }
322
323
        return $return;
324
    }
325
326
    /**
327
     * @param string $storeKey
328
     *
329
     * @return bool
330
     */
331 67
    protected function checkForStaticCache(string $storeKey): bool
332
    {
333 67
        return !empty(self::$STATIC_CACHE)
334
               &&
335 67
               \array_key_exists($storeKey, self::$STATIC_CACHE)
336
               &&
337 67
               \array_key_exists($storeKey, self::$STATIC_CACHE_EXPIRE)
338
               &&
339 67
               \time() <= self::$STATIC_CACHE_EXPIRE[$storeKey];
340
    }
341
342
    /**
343
     * Clean store-key (required e.g. for the "File"-Adapter).
344
     *
345
     * @param string $str
346
     *
347
     * @return string
348
     */
349 57
    protected function cleanStoreKey(string $str): string
350
    {
351 57
        return \md5($str);
352
    }
353
354
    /**
355
     * Check if cached-item exists.
356
     *
357
     * @param string $key
358
     *
359
     * @return bool
360
     */
361 24
    public function existsItem(string $key): bool
362
    {
363 24
        if (!$this->adapter instanceof iAdapter) {
364
            return false;
365
        }
366
367 24
        $storeKey = $this->calculateStoreKey($key);
368
369
        // check static-cache
370 24
        if ($this->checkForStaticCache($storeKey)) {
371
            return true;
372
        }
373
374 24
        return $this->adapter->exists($storeKey);
375
    }
376
377
    /**
378
     * Get cached-item by key.
379
     *
380
     * @param string $key
381
     * @param int    $forceStaticCacheHitCounter
382
     *
383
     * @return mixed
384
     */
385 56
    public function getItem(string $key, int $forceStaticCacheHitCounter = 0)
386
    {
387 56
        if (!$this->adapter instanceof iAdapter) {
388
            return null;
389
        }
390
391 56
        $storeKey = $this->calculateStoreKey($key);
392
393
        // check if we already using static-cache
394 56
        $useStaticCache = true;
395 56
        if ($this->adapter instanceof AdapterArray) {
396 12
            $useStaticCache = false;
397
        }
398
399 56
        if (!isset(self::$STATIC_CACHE_COUNTER[$storeKey])) {
400 34
            self::$STATIC_CACHE_COUNTER[$storeKey] = 0;
401
        }
402
403
        // get from static-cache
404
        if (
405 56
            $useStaticCache
406
            &&
407 56
            $this->checkForStaticCache($storeKey)
408
        ) {
409 11
            return self::$STATIC_CACHE[$storeKey];
410
        }
411
412 53
        $serialized = $this->adapter->get($storeKey);
413 53
        $value = $serialized && $this->serializer ? $this->serializer->unserialize($serialized) : null;
414
415 53
        self::$STATIC_CACHE_COUNTER[$storeKey]++;
416
417
        // save into static-cache if needed
418
        if (
419 53
            $useStaticCache
420
            &&
421
            (
422
                (
423 41
                    $forceStaticCacheHitCounter !== 0
424
                    &&
425 1
                    self::$STATIC_CACHE_COUNTER[$storeKey] >= $forceStaticCacheHitCounter
426
                )
427
                ||
428
                (
429 41
                    $this->staticCacheHitCounter !== 0
430
                    &&
431 53
                    self::$STATIC_CACHE_COUNTER[$storeKey] >= $this->staticCacheHitCounter
432
                )
433
            )
434
        ) {
435 13
            self::$STATIC_CACHE[$storeKey] = $value;
436
        }
437
438 53
        return $value;
439
    }
440
441
    /**
442
     * Remove all cached-items.
443
     *
444
     * @return bool
445
     */
446 5
    public function removeAll(): bool
447
    {
448 5
        if (!$this->adapter instanceof iAdapter) {
449
            return false;
450
        }
451
452
        // remove static-cache
453 5
        if (!empty(self::$STATIC_CACHE)) {
454 5
            self::$STATIC_CACHE = [];
455 5
            self::$STATIC_CACHE_COUNTER = [];
456 5
            self::$STATIC_CACHE_EXPIRE = [];
457
        }
458
459 5
        return $this->adapter->removeAll();
460
    }
461
462
    /**
463
     * Remove a cached-item.
464
     *
465
     * @param string $key
466
     *
467
     * @return bool
468
     */
469 10
    public function removeItem(string $key): bool
470
    {
471 10
        if (!$this->adapter instanceof iAdapter) {
472
            return false;
473
        }
474
475 10
        $storeKey = $this->calculateStoreKey($key);
476
477
        // remove static-cache
478
        if (
479 10
            !empty(self::$STATIC_CACHE)
480
            &&
481 10
            \array_key_exists($storeKey, self::$STATIC_CACHE)
482
        ) {
483
            unset(
484
                self::$STATIC_CACHE[$storeKey],
485
                self::$STATIC_CACHE_COUNTER[$storeKey],
486
                self::$STATIC_CACHE_EXPIRE[$storeKey]
487
            );
488
        }
489
490 10
        return $this->adapter->remove($storeKey);
491
    }
492
493
    /**
494
     * Set cache-item by key => value + ttl.
495
     *
496
     * @param string                 $key
497
     * @param mixed                  $value
498
     * @param \DateInterval|int|null $ttl
499
     *
500
     * @throws \InvalidArgumentException
501
     *
502
     * @return bool
503
     */
504 57
    public function setItem(string $key, $value, $ttl = 0): bool
505
    {
506
        if (
507 57
            !$this->adapter instanceof iAdapter
508
            ||
509 57
            !$this->serializer instanceof iSerializer
510
        ) {
511
            return false;
512
        }
513
514 57
        $storeKey = $this->calculateStoreKey($key);
515 57
        $serialized = $this->serializer->serialize($value);
516
517
        // update static-cache, if it's exists
518 57
        if (\array_key_exists($storeKey, self::$STATIC_CACHE)) {
519 8
            self::$STATIC_CACHE[$storeKey] = $value;
520
        }
521
522 57
        if ($ttl) {
523 32
            if ($ttl instanceof \DateInterval) {
524
                // Converting to a TTL in seconds
525 1
                $ttl = (new \DateTimeImmutable('now'))->add($ttl)->getTimestamp() - \time();
526
            }
527
528
            // always cache the TTL time, maybe we need this later ...
529 32
            self::$STATIC_CACHE_EXPIRE[$storeKey] = ($ttl ? (int) $ttl + \time() : 0);
530
531 32
            return $this->adapter->setExpired($storeKey, $serialized, $ttl);
532
        }
533
534 25
        return $this->adapter->set($storeKey, $serialized);
535
    }
536
537
    /**
538
     * Set cache-item by key => value + date.
539
     *
540
     * @param string             $key
541
     * @param mixed              $value
542
     * @param \DateTimeInterface $date <p>If the date is in the past, we will remove the existing cache-item.</p>
543
     *
544
     * @throws InvalidArgumentException
545
     *                                   <p>If the $date is in the past.</p>
546
     *
547
     * @return bool
548
     */
549 26
    public function setItemToDate(string $key, $value, \DateTimeInterface $date): bool
550
    {
551 26
        $ttl = $date->getTimestamp() - \time();
552
553 26
        if ($ttl <= 0) {
554 4
            throw new InvalidArgumentException('Date in the past.');
555
        }
556
557 22
        return $this->setItem($key, $value, $ttl);
558
    }
559
560
    /**
561
     * Get the "isReady" state.
562
     *
563
     * @return bool
564
     */
565 8
    public function getCacheIsReady(): bool
566
    {
567 8
        return $this->isReady;
568
    }
569
570
    /**
571
     * returns the IP address of the client
572
     *
573
     * @param bool $trust_proxy_headers     <p>
574
     *                                      Whether or not to trust the
575
     *                                      proxy headers HTTP_CLIENT_IP
576
     *                                      and HTTP_X_FORWARDED_FOR. ONLY
577
     *                                      use if your $_SERVER is behind a
578
     *                                      proxy that sets these values
579
     *                                      </p>
580
     *
581
     * @return string
582
     */
583
    protected function getClientIp(bool $trust_proxy_headers = false): string
584
    {
585
        $remoteAddr = $_SERVER['REMOTE_ADDR'] ?? 'NO_REMOTE_ADDR';
586
587
        if ($trust_proxy_headers) {
588
            return $remoteAddr;
589
        }
590
591
        if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
592
            $ip = $_SERVER['HTTP_CLIENT_IP'];
593
        } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
594
            $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
595
        } else {
596
            $ip = $remoteAddr;
597
        }
598
599
        return $ip;
600
    }
601
602
    /**
603
     * Get the prefix.
604
     *
605
     * @return string
606
     */
607 99
    public function getPrefix(): string
608
    {
609 99
        return $this->prefix;
610
    }
611
612
    /**
613
     * Get the current value, when the static cache is used.
614
     *
615
     * @return int
616
     */
617
    public function getStaticCacheHitCounter(): int
618
    {
619
        return $this->staticCacheHitCounter;
620
    }
621
622
    /**
623
     * Set the default-prefix via "SERVER"-var + "SESSION"-language.
624
     *
625
     * @return string
626
     */
627 119
    protected function getTheDefaultPrefix(): string
628
    {
629 119
        return ($_SERVER['SERVER_NAME'] ?? '') . '_' .
630 119
               ($_SERVER['THEME'] ?? '') . '_' .
631 119
               ($_SERVER['STAGE'] ?? '') . '_' .
632 119
               ($_SESSION['language'] ?? '') . '_' .
633 119
               ($_SESSION['language_extra'] ?? '') . '_' .
634 119
               \PHP_VERSION_ID . '_' .
635 119
               ($this->serializer ? $this->serializer->getName() : '');
636
    }
637
638
    /**
639
     * Get the current adapter class-name.
640
     *
641
     * @return string
642
     *
643
     * @psalm-return class-string|string
644
     */
645 4
    public function getUsedAdapterClassName(): string
646
    {
647 4
        if ($this->adapter) {
648 4
            return \get_class($this->adapter);
649
        }
650
651
        return '';
652
    }
653
654
    /**
655
     * Get the current serializer class-name.
656
     *
657
     * @return string
658
     *
659
     * @psalm-return class-string|string
660
     */
661 4
    public function getUsedSerializerClassName(): string
662
    {
663 4
        if ($this->serializer) {
664 4
            return \get_class($this->serializer);
665
        }
666
667
        return '';
668
    }
669
670
    /**
671
     * check if the current use is a admin || dev || server == client
672
     *
673
     * @return bool
674
     */
675
    public function isCacheActiveForTheCurrentUser(): bool
676
    {
677
        // init
678
        $active = true;
679
680
        // test the cache, with this GET-parameter
681
        if ($this->disableCacheGetParameter) {
682
            $testCache = isset($_GET[$this->disableCacheGetParameter]) ? (int) $_GET[$this->disableCacheGetParameter] : 0;
683
        } else {
684
            $testCache = 0;
685
        }
686
687
        if ($testCache !== 1) {
688
            if (
689
                // admin session is active
690
                (
691
                    $this->useCheckForAdminSession
692
                    &&
693
                    $this->isAdminSession
694
                )
695
                ||
696
                // server == client
697
                (
698
                    $this->useCheckForServerIpIsClientIp
699
                    &&
700
                    isset($_SERVER['SERVER_ADDR'])
701
                    &&
702
                    $_SERVER['SERVER_ADDR'] === $this->getClientIp()
703
                )
704
                ||
705
                // user is a dev
706
                (
707
                    $this->useCheckForDev
708
                    &&
709
                    $this->checkForDev()
710
                )
711
            ) {
712
                $active = false;
713
            }
714
        }
715
716
        return $active;
717
    }
718
719
    /**
720
     * enable / disable the cache
721
     *
722
     * @param bool $isActive
723
     *
724
     * @return void
725
     */
726
    public function setActive(bool $isActive)
727
    {
728
        $this->isActive = $isActive;
729
    }
730
731
    /**
732
     * Set "isReady" state.
733
     *
734
     * @param bool $isReady
735
     *
736
     * @return void
737
     */
738 119
    protected function setCacheIsReady(bool $isReady)
739
    {
740 119
        $this->isReady = $isReady;
741 119
    }
742
743
    /**
744
     * !!! Set the prefix. !!!
745
     *
746
     * WARNING: Do not use if you don't know what you do. Because this will overwrite the default prefix.
747
     *
748
     * @param string $prefix
749
     *
750
     * @return void
751
     */
752 119
    public function setPrefix(string $prefix)
753
    {
754 119
        $this->prefix = $prefix;
755 119
    }
756
757
    /**
758
     * Set the static-hit-counter: Who often do we hit the cache, before we use static cache?
759
     *
760
     * @param int $staticCacheHitCounter
761
     *
762
     * @return void
763
     */
764
    public function setStaticCacheHitCounter(int $staticCacheHitCounter)
765
    {
766
        $this->staticCacheHitCounter = $staticCacheHitCounter;
767
    }
768
}
769