Completed
Push — master ( 0e0464...f097a8 )
by Lars
01:40
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
        if ($this->serializer && $this->serializer instanceof SerializerNo) {
414
            $value = $serialized;
415
        } else {
416 53
            $value = $serialized && $this->serializer ? $this->serializer->unserialize($serialized) : null;
417
        }
418
419 53
        self::$STATIC_CACHE_COUNTER[$storeKey]++;
420
421
        // save into static-cache if needed
422
        if (
423 53
            $useStaticCache
424
            &&
425
            (
426
                (
427 41
                    $forceStaticCacheHitCounter !== 0
428
                    &&
429 1
                    self::$STATIC_CACHE_COUNTER[$storeKey] >= $forceStaticCacheHitCounter
430
                )
431
                ||
432
                (
433 41
                    $this->staticCacheHitCounter !== 0
434
                    &&
435 53
                    self::$STATIC_CACHE_COUNTER[$storeKey] >= $this->staticCacheHitCounter
436
                )
437
            )
438
        ) {
439 13
            self::$STATIC_CACHE[$storeKey] = $value;
440
        }
441
442 53
        return $value;
443
    }
444
445
    /**
446
     * Remove all cached-items.
447
     *
448
     * @return bool
449
     */
450 5
    public function removeAll(): bool
451
    {
452 5
        if (!$this->adapter instanceof iAdapter) {
453
            return false;
454
        }
455
456
        // remove static-cache
457 5
        if (!empty(self::$STATIC_CACHE)) {
458 5
            self::$STATIC_CACHE = [];
459 5
            self::$STATIC_CACHE_COUNTER = [];
460 5
            self::$STATIC_CACHE_EXPIRE = [];
461
        }
462
463 5
        return $this->adapter->removeAll();
464
    }
465
466
    /**
467
     * Remove a cached-item.
468
     *
469
     * @param string $key
470
     *
471
     * @return bool
472
     */
473 10
    public function removeItem(string $key): bool
474
    {
475 10
        if (!$this->adapter instanceof iAdapter) {
476
            return false;
477
        }
478
479 10
        $storeKey = $this->calculateStoreKey($key);
480
481
        // remove static-cache
482
        if (
483 10
            !empty(self::$STATIC_CACHE)
484
            &&
485 10
            \array_key_exists($storeKey, self::$STATIC_CACHE)
486
        ) {
487
            unset(
488
                self::$STATIC_CACHE[$storeKey],
489
                self::$STATIC_CACHE_COUNTER[$storeKey],
490
                self::$STATIC_CACHE_EXPIRE[$storeKey]
491
            );
492
        }
493
494 10
        return $this->adapter->remove($storeKey);
495
    }
496
497
    /**
498
     * Set cache-item by key => value + ttl.
499
     *
500
     * @param string                 $key
501
     * @param mixed                  $value
502
     * @param \DateInterval|int|null $ttl
503
     *
504
     * @throws \InvalidArgumentException
505
     *
506
     * @return bool
507
     */
508 57
    public function setItem(string $key, $value, $ttl = 0): bool
509
    {
510
        if (
511 57
            !$this->adapter instanceof iAdapter
512
            ||
513 57
            !$this->serializer instanceof iSerializer
514
        ) {
515
            return false;
516
        }
517
518 57
        $storeKey = $this->calculateStoreKey($key);
519 57
        $serialized = $this->serializer->serialize($value);
520
521
        // update static-cache, if it's exists
522 57
        if (\array_key_exists($storeKey, self::$STATIC_CACHE)) {
523 8
            self::$STATIC_CACHE[$storeKey] = $value;
524
        }
525
526 57
        if ($ttl) {
527 32
            if ($ttl instanceof \DateInterval) {
528
                // Converting to a TTL in seconds
529 1
                $ttl = (new \DateTimeImmutable('now'))->add($ttl)->getTimestamp() - \time();
530
            }
531
532
            // always cache the TTL time, maybe we need this later ...
533 32
            self::$STATIC_CACHE_EXPIRE[$storeKey] = ($ttl ? (int) $ttl + \time() : 0);
534
535 32
            return $this->adapter->setExpired($storeKey, $serialized, $ttl);
536
        }
537
538 25
        return $this->adapter->set($storeKey, $serialized);
539
    }
540
541
    /**
542
     * Set cache-item by key => value + date.
543
     *
544
     * @param string             $key
545
     * @param mixed              $value
546
     * @param \DateTimeInterface $date <p>If the date is in the past, we will remove the existing cache-item.</p>
547
     *
548
     * @throws InvalidArgumentException
549
     *                                   <p>If the $date is in the past.</p>
550
     *
551
     * @return bool
552
     */
553 26
    public function setItemToDate(string $key, $value, \DateTimeInterface $date): bool
554
    {
555 26
        $ttl = $date->getTimestamp() - \time();
556
557 26
        if ($ttl <= 0) {
558 4
            throw new InvalidArgumentException('Date in the past.');
559
        }
560
561 22
        return $this->setItem($key, $value, $ttl);
562
    }
563
564
    /**
565
     * Get the "isReady" state.
566
     *
567
     * @return bool
568
     */
569 8
    public function getCacheIsReady(): bool
570
    {
571 8
        return $this->isReady;
572
    }
573
574
    /**
575
     * returns the IP address of the client
576
     *
577
     * @param bool $trust_proxy_headers     <p>
578
     *                                      Whether or not to trust the
579
     *                                      proxy headers HTTP_CLIENT_IP
580
     *                                      and HTTP_X_FORWARDED_FOR. ONLY
581
     *                                      use if your $_SERVER is behind a
582
     *                                      proxy that sets these values
583
     *                                      </p>
584
     *
585
     * @return string
586
     */
587
    protected function getClientIp(bool $trust_proxy_headers = false): string
588
    {
589
        $remoteAddr = $_SERVER['REMOTE_ADDR'] ?? 'NO_REMOTE_ADDR';
590
591
        if ($trust_proxy_headers) {
592
            return $remoteAddr;
593
        }
594
595
        if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
596
            $ip = $_SERVER['HTTP_CLIENT_IP'];
597
        } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
598
            $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
599
        } else {
600
            $ip = $remoteAddr;
601
        }
602
603
        return $ip;
604
    }
605
606
    /**
607
     * Get the prefix.
608
     *
609
     * @return string
610
     */
611 99
    public function getPrefix(): string
612
    {
613 99
        return $this->prefix;
614
    }
615
616
    /**
617
     * Get the current value, when the static cache is used.
618
     *
619
     * @return int
620
     */
621
    public function getStaticCacheHitCounter(): int
622
    {
623
        return $this->staticCacheHitCounter;
624
    }
625
626
    /**
627
     * Set the default-prefix via "SERVER"-var + "SESSION"-language.
628
     *
629
     * @return string
630
     */
631 119
    protected function getTheDefaultPrefix(): string
632
    {
633 119
        return ($_SERVER['SERVER_NAME'] ?? '') . '_' .
634 119
               ($_SERVER['THEME'] ?? '') . '_' .
635 119
               ($_SERVER['STAGE'] ?? '') . '_' .
636 119
               ($_SESSION['language'] ?? '') . '_' .
637 119
               ($_SESSION['language_extra'] ?? '') . '_' .
638 119
               \PHP_VERSION_ID . '_' .
639 119
               ($this->serializer ? $this->serializer->getName() : '');
640
    }
641
642
    /**
643
     * Get the current adapter class-name.
644
     *
645
     * @return string
646
     *
647
     * @psalm-return class-string|string
648
     */
649 4
    public function getUsedAdapterClassName(): string
650
    {
651 4
        if ($this->adapter) {
652 4
            return \get_class($this->adapter);
653
        }
654
655
        return '';
656
    }
657
658
    /**
659
     * Get the current serializer class-name.
660
     *
661
     * @return string
662
     *
663
     * @psalm-return class-string|string
664
     */
665 4
    public function getUsedSerializerClassName(): string
666
    {
667 4
        if ($this->serializer) {
668 4
            return \get_class($this->serializer);
669
        }
670
671
        return '';
672
    }
673
674
    /**
675
     * check if the current use is a admin || dev || server == client
676
     *
677
     * @return bool
678
     */
679
    public function isCacheActiveForTheCurrentUser(): bool
680
    {
681
        // init
682
        $active = true;
683
684
        // test the cache, with this GET-parameter
685
        if ($this->disableCacheGetParameter) {
686
            $testCache = isset($_GET[$this->disableCacheGetParameter]) ? (int) $_GET[$this->disableCacheGetParameter] : 0;
687
        } else {
688
            $testCache = 0;
689
        }
690
691
        if ($testCache !== 1) {
692
            if (
693
                // admin session is active
694
                (
695
                    $this->useCheckForAdminSession
696
                    &&
697
                    $this->isAdminSession
698
                )
699
                ||
700
                // server == client
701
                (
702
                    $this->useCheckForServerIpIsClientIp
703
                    &&
704
                    isset($_SERVER['SERVER_ADDR'])
705
                    &&
706
                    $_SERVER['SERVER_ADDR'] === $this->getClientIp()
707
                )
708
                ||
709
                // user is a dev
710
                (
711
                    $this->useCheckForDev
712
                    &&
713
                    $this->checkForDev()
714
                )
715
            ) {
716
                $active = false;
717
            }
718
        }
719
720
        return $active;
721
    }
722
723
    /**
724
     * enable / disable the cache
725
     *
726
     * @param bool $isActive
727
     *
728
     * @return void
729
     */
730
    public function setActive(bool $isActive)
731
    {
732
        $this->isActive = $isActive;
733
    }
734
735
    /**
736
     * Set "isReady" state.
737
     *
738
     * @param bool $isReady
739
     *
740
     * @return void
741
     */
742 119
    protected function setCacheIsReady(bool $isReady)
743
    {
744 119
        $this->isReady = $isReady;
745 119
    }
746
747
    /**
748
     * !!! Set the prefix. !!!
749
     *
750
     * WARNING: Do not use if you don't know what you do. Because this will overwrite the default prefix.
751
     *
752
     * @param string $prefix
753
     *
754
     * @return void
755
     */
756 119
    public function setPrefix(string $prefix)
757
    {
758 119
        $this->prefix = $prefix;
759 119
    }
760
761
    /**
762
     * Set the static-hit-counter: Who often do we hit the cache, before we use static cache?
763
     *
764
     * @param int $staticCacheHitCounter
765
     *
766
     * @return void
767
     */
768
    public function setStaticCacheHitCounter(int $staticCacheHitCounter)
769
    {
770
        $this->staticCacheHitCounter = $staticCacheHitCounter;
771
    }
772
}
773