Completed
Push — master ( 94f5e8...a88b9e )
by Lars
01:57 queued 11s
created

Cache::__construct()   C

Complexity

Conditions 11
Paths 28

Size

Total Lines 69

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 11.055

Importance

Changes 0
Metric Value
dl 0
loc 69
ccs 24
cts 26
cp 0.9231
rs 6.5296
c 0
b 0
f 0
cc 11
nc 28
nop 11
crap 11.055

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