Completed
Push — master ( 64a3e6...ccead9 )
by Lars
02:05
created

Cache::__construct()   C

Complexity

Conditions 11
Paths 28

Size

Total Lines 67

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 11.0619

Importance

Changes 0
Metric Value
dl 0
loc 67
ccs 23
cts 25
cp 0.92
rs 6.5733
c 0
b 0
f 0
cc 11
nc 28
nop 11
crap 11.0619

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