Completed
Push — master ( 67b201...64a3e6 )
by Lars
02:31 queued 11s
created

Cache::__construct()   C

Complexity

Conditions 11
Paths 28

Size

Total Lines 67

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 12.6727

Importance

Changes 0
Metric Value
dl 0
loc 67
ccs 19
cts 25
cp 0.76
rs 6.5733
c 0
b 0
f 0
cc 11
nc 28
nop 11
crap 12.6727

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 65
    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 65
        $this->isAdminSession = $isAdminSession;
132
133 65
        $this->useCheckForDev = $useCheckForDev;
134 65
        $this->useCheckForAdminSession = $useCheckForAdminSession;
135 65
        $this->useCheckForServerIpIsClientIp = $useCheckForServerIpIsClientIp;
136
137 65
        $this->disableCacheGetParameter = $disableCacheGetParameter;
138
139
        // First check if the cache is active at all.
140 65
        $this->isActive = $cacheEnabled;
141
        if (
142 65
            $this->isActive
143
            &&
144 65
            $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 65
        if ($this->isActive) {
151 65
            $this->setPrefix($this->getTheDefaultPrefix());
152
153 65
            if ($adapter === null) {
154
                $adapter = $this->autoConnectToAvailableCacheSystem($cacheAdapterManagerForAutoConnect, $cacheAdapterManagerForAutoConnectOverwrite);
155
            }
156
157
            // INFO: Memcache(d) has his own "serializer", so don't use it twice
158 65
            if (!\is_object($serializer) && $serializer === null) {
159
                if (
160
                    $adapter instanceof AdapterMemcached
161
                    ||
162
                    $adapter instanceof AdapterMemcache
163
                ) {
164
                    $serializer = new SerializerNo();
165
                } else {
166
                    // set default serializer
167
                    $serializer = new SerializerIgbinary();
168
                }
169
            }
170
        }
171
172
        // Final checks ...
173
        if (
174 65
            $serializer !== null
175
            &&
176 65
            $adapter !== null
177
        ) {
178 65
            $this->setCacheIsReady(true);
179
180 65
            $this->adapter = $adapter;
181 65
            $this->serializer = $serializer;
182
        }
183 65
    }
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
    protected function autoConnectToAvailableCacheSystem(
196
        CacheAdapterAutoManager $cacheAdapterManagerForAutoConnect = null,
197
        bool $cacheAdapterManagerForAutoConnectOverwrite = false
198
    ): iAdapter
199
    {
200
        static $AUTO_ADAPTER_STATIC_CACHE = null;
201
202
        if (
203
            \is_object($AUTO_ADAPTER_STATIC_CACHE)
204
            &&
205
            $AUTO_ADAPTER_STATIC_CACHE instanceof iAdapter
206
        ) {
207
            return $AUTO_ADAPTER_STATIC_CACHE;
208
        }
209
210
        // init
211
        $adapter = null;
212
213
        $cacheAdapterManagerDefault = CacheAdapterAutoManager::getDefaultsForAutoInit();
214
215
        if ($cacheAdapterManagerForAutoConnect !== null) {
216
            if ($cacheAdapterManagerForAutoConnectOverwrite) {
217
                $cacheAdapterManagerDefault = $cacheAdapterManagerForAutoConnect;
218
            } else {
219
                /** @noinspection PhpUnhandledExceptionInspection */
220
                $cacheAdapterManagerDefault->merge($cacheAdapterManagerForAutoConnect);
221
            }
222
        }
223
224
        foreach ($cacheAdapterManagerDefault->getAdapters() as $adapterTmp => $callableFunctionTmp) {
225
226
            /** @var iAdapter $adapterTest */
227
            if ($callableFunctionTmp !== null) {
228
                $adapterTest = new $adapterTmp($callableFunctionTmp);
229
            } else {
230
                $adapterTest = new $adapterTmp();
231
            }
232
233
            if ($adapterTest->installed()) {
234
                $adapter = $adapterTest;
235
236
                break;
237
            }
238
        }
239
240
        // save to static cache
241
        $AUTO_ADAPTER_STATIC_CACHE = $adapter;
242
243
        return $adapter;
244
    }
245
246
    /**
247
     * Calculate store-key (prefix + $rawKey).
248
     *
249
     * @param string $rawKey
250
     *
251
     * @return string
252
     */
253 53
    protected function calculateStoreKey(string $rawKey): string
254
    {
255 53
        $str = $this->getPrefix() . $rawKey;
256
257 53
        if ($this->adapter instanceof AdapterFileAbstract) {
258 24
            $str = $this->cleanStoreKey($str);
259
        }
260
261 53
        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 36
    protected function checkForStaticCache(string $storeKey): bool
305
    {
306 36
        return !empty(self::$STATIC_CACHE)
307
               &&
308 36
               \array_key_exists($storeKey, self::$STATIC_CACHE)
309
               &&
310 36
               \array_key_exists($storeKey, self::$STATIC_CACHE_EXPIRE)
311
               &&
312 36
               \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 24
    protected function cleanStoreKey(string $str): string
323
    {
324 24
        return \md5($str);
325
    }
326
327
    /**
328
     * Check if cached-item exists.
329
     *
330
     * @param string $key
331
     *
332
     * @return bool
333
     */
334 11
    public function existsItem(string $key): bool
335
    {
336 11
        if (!$this->adapter instanceof iAdapter) {
337
            return false;
338
        }
339
340 11
        $storeKey = $this->calculateStoreKey($key);
341
342
        // check static-cache
343 11
        if ($this->checkForStaticCache($storeKey)) {
344
            return true;
345
        }
346
347 11
        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 31
    public function getItem(string $key, int $forceStaticCacheHitCounter = 0)
359
    {
360 31
        if (!$this->adapter instanceof iAdapter) {
361
            return null;
362
        }
363
364 31
        $storeKey = $this->calculateStoreKey($key);
365
366
        // check if we already using static-cache
367 31
        $useStaticCache = true;
368 31
        if ($this->adapter instanceof AdapterArray) {
369 6
            $useStaticCache = false;
370
        }
371
372 31
        if (!isset(self::$STATIC_CACHE_COUNTER[$storeKey])) {
373 24
            self::$STATIC_CACHE_COUNTER[$storeKey] = 0;
374
        }
375
376
        // get from static-cache
377
        if (
378 31
            $useStaticCache
379
            &&
380 31
            $this->checkForStaticCache($storeKey)
381
        ) {
382 8
            return self::$STATIC_CACHE[$storeKey];
383
        }
384
385 29
        $serialized = $this->adapter->get($storeKey);
386 29
        $value = $serialized && $this->serializer ? $this->serializer->unserialize($serialized) : null;
387
388 29
        self::$STATIC_CACHE_COUNTER[$storeKey]++;
389
390
        // save into static-cache if needed
391
        if (
392 29
            $useStaticCache
393
            &&
394
            (
395
                (
396 23
                    $forceStaticCacheHitCounter !== 0
397
                    &&
398
                    self::$STATIC_CACHE_COUNTER[$storeKey] >= $forceStaticCacheHitCounter
399
                )
400
                ||
401
                (
402 23
                    $this->staticCacheHitCounter !== 0
403
                    &&
404 29
                    self::$STATIC_CACHE_COUNTER[$storeKey] >= $this->staticCacheHitCounter
405
                )
406
            )
407
        ) {
408 9
            self::$STATIC_CACHE[$storeKey] = $value;
409
        }
410
411 29
        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 5
    public function removeItem(string $key): bool
443
    {
444 5
        if (!$this->adapter instanceof iAdapter) {
445
            return false;
446
        }
447
448 5
        $storeKey = $this->calculateStoreKey($key);
449
450
        // remove static-cache
451
        if (
452 5
            !empty(self::$STATIC_CACHE)
453
            &&
454 5
            \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 5
        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 32
    public function setItem(string $key, $value, $ttl = 0): bool
478
    {
479
        if (
480 32
            !$this->adapter instanceof iAdapter
481
            ||
482 32
            !$this->serializer instanceof iSerializer
483
        ) {
484
            return false;
485
        }
486
487 32
        $storeKey = $this->calculateStoreKey($key);
488 32
        $serialized = $this->serializer->serialize($value);
489
490
        // update static-cache, if it's exists
491 32
        if (\array_key_exists($storeKey, self::$STATIC_CACHE)) {
492 5
            self::$STATIC_CACHE[$storeKey] = $value;
493
        }
494
495 32
        if ($ttl) {
496 16
            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 16
            self::$STATIC_CACHE_EXPIRE[$storeKey] = ($ttl ? (int) $ttl + \time() : 0);
504
505 16
            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 13
    public function setItemToDate(string $key, $value, \DateTimeInterface $date): bool
524
    {
525 13
        $ttl = $date->getTimestamp() - \time();
526
527 13
        if ($ttl <= 0) {
528 1
            throw new InvalidArgumentException('Date in the past.');
529
        }
530
531 12
        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 53
    public function getPrefix(): string
582
    {
583 53
        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 65
    protected function getTheDefaultPrefix(): string
600
    {
601 65
        return ($_SERVER['SERVER_NAME'] ?? '') . '_' .
602 65
               ($_SERVER['THEME'] ?? '') . '_' .
603 65
               ($_SERVER['STAGE'] ?? '') . '_' .
604 65
               ($_SESSION['language'] ?? '') . '_' .
605 65
               ($_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 65
    protected function setCacheIsReady(bool $isReady)
702
    {
703 65
        $this->isReady = $isReady;
704 65
    }
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 65
    public function setPrefix(string $prefix)
714
    {
715 65
        $this->prefix = $prefix;
716 65
    }
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