Completed
Push — master ( dfda1d...2f160c )
by Lars
04:17 queued 18s
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 89
    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 89
        $this->isAdminSession = $isAdminSession;
139
140 89
        $this->useCheckForDev = $useCheckForDev;
141 89
        $this->useCheckForAdminSession = $useCheckForAdminSession;
142 89
        $this->useCheckForServerIpIsClientIp = $useCheckForServerIpIsClientIp;
143
144 89
        $this->disableCacheGetParameter = $disableCacheGetParameter;
145
146
        // First check if the cache is active at all.
147 89
        $this->isActive = $cacheEnabled;
148
        if (
149 89
            $this->isActive
150
            &&
151 89
            $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 89
        if ($this->isActive) {
158 89
            $this->setPrefix($this->getTheDefaultPrefix());
159
160 89
            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 89
            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 89
            $serializer !== null
182
            &&
183 89
            $adapter !== null
184
        ) {
185 89
            $this->setCacheIsReady(true);
186
187 89
            $this->adapter = $adapter;
188
189 89
            $this->serializer = $serializer;
190
191 89
            $this->serializer->setUnserializeOptions($this->unserialize_options);
192
        }
193 89
    }
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 73
    protected function calculateStoreKey(string $rawKey): string
272
    {
273 73
        $str = $this->getPrefix() . $rawKey;
274
275 73
        if ($this->adapter instanceof AdapterFileAbstract) {
276 44
            $str = $this->cleanStoreKey($str);
277
        }
278
279 73
        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 44
    protected function checkForStaticCache(string $storeKey): bool
323
    {
324 44
        return !empty(self::$STATIC_CACHE)
325
               &&
326 44
               \array_key_exists($storeKey, self::$STATIC_CACHE)
327
               &&
328 44
               \array_key_exists($storeKey, self::$STATIC_CACHE_EXPIRE)
329
               &&
330 44
               \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 44
    protected function cleanStoreKey(string $str): string
341
    {
342 44
        return \md5($str);
343
    }
344
345
    /**
346
     * Check if cached-item exists.
347
     *
348
     * @param string $key
349
     *
350
     * @return bool
351
     */
352 12
    public function existsItem(string $key): bool
353
    {
354 12
        if (!$this->adapter instanceof iAdapter) {
355
            return false;
356
        }
357
358 12
        $storeKey = $this->calculateStoreKey($key);
359
360
        // check static-cache
361 12
        if ($this->checkForStaticCache($storeKey)) {
362
            return true;
363
        }
364
365 12
        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 38
    public function getItem(string $key, int $forceStaticCacheHitCounter = 0)
377
    {
378 38
        if (!$this->adapter instanceof iAdapter) {
379
            return null;
380
        }
381
382 38
        $storeKey = $this->calculateStoreKey($key);
383
384
        // check if we already using static-cache
385 38
        $useStaticCache = true;
386 38
        if ($this->adapter instanceof AdapterArray) {
387 6
            $useStaticCache = false;
388
        }
389
390 38
        if (!isset(self::$STATIC_CACHE_COUNTER[$storeKey])) {
391 16
            self::$STATIC_CACHE_COUNTER[$storeKey] = 0;
392
        }
393
394
        // get from static-cache
395
        if (
396 38
            $useStaticCache
397
            &&
398 38
            $this->checkForStaticCache($storeKey)
399
        ) {
400 2
            return self::$STATIC_CACHE[$storeKey];
401
        }
402
403 38
        $serialized = $this->adapter->get($storeKey);
404 28
        $value = $serialized && $this->serializer ? $this->serializer->unserialize($serialized) : null;
405
406 28
        self::$STATIC_CACHE_COUNTER[$storeKey]++;
407
408
        // save into static-cache if needed
409
        if (
410 28
            $useStaticCache
411
            &&
412
            (
413
                (
414 22
                    $forceStaticCacheHitCounter !== 0
415
                    &&
416
                    self::$STATIC_CACHE_COUNTER[$storeKey] >= $forceStaticCacheHitCounter
417
                )
418
                ||
419
                (
420 22
                    $this->staticCacheHitCounter !== 0
421
                    &&
422 28
                    self::$STATIC_CACHE_COUNTER[$storeKey] >= $this->staticCacheHitCounter
423
                )
424
            )
425
        ) {
426 3
            self::$STATIC_CACHE[$storeKey] = $value;
427
        }
428
429 28
        return $value;
430
    }
431
432
    /**
433
     * Remove all cached-items.
434
     *
435
     * @return bool
436
     */
437 1
    public function removeAll(): bool
438
    {
439 1
        if (!$this->adapter instanceof iAdapter) {
440
            return false;
441
        }
442
443
        // remove static-cache
444 1
        if (!empty(self::$STATIC_CACHE)) {
445 1
            self::$STATIC_CACHE = [];
446 1
            self::$STATIC_CACHE_COUNTER = [];
447 1
            self::$STATIC_CACHE_EXPIRE = [];
448
        }
449
450 1
        return $this->adapter->removeAll();
451
    }
452
453
    /**
454
     * Remove a cached-item.
455
     *
456
     * @param string $key
457
     *
458
     * @return bool
459
     */
460 6
    public function removeItem(string $key): bool
461
    {
462 6
        if (!$this->adapter instanceof iAdapter) {
463
            return false;
464
        }
465
466 6
        $storeKey = $this->calculateStoreKey($key);
467
468
        // remove static-cache
469
        if (
470 6
            !empty(self::$STATIC_CACHE)
471
            &&
472 6
            \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 6
        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 41
    public function setItem(string $key, $value, $ttl = 0): bool
496
    {
497
        if (
498 41
            !$this->adapter instanceof iAdapter
499
            ||
500 41
            !$this->serializer instanceof iSerializer
501
        ) {
502
            return false;
503
        }
504
505 41
        $storeKey = $this->calculateStoreKey($key);
506 41
        $serialized = $this->serializer->serialize($value);
507
508
        // update static-cache, if it's exists
509 41
        if (\array_key_exists($storeKey, self::$STATIC_CACHE)) {
510 1
            self::$STATIC_CACHE[$storeKey] = $value;
511
        }
512
513 41
        if ($ttl) {
514 25
            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 25
            self::$STATIC_CACHE_EXPIRE[$storeKey] = ($ttl ? (int) $ttl + \time() : 0);
522
523 25
            return $this->adapter->setExpired($storeKey, $serialized, $ttl);
524
        }
525
526 16
        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 19
    public function setItemToDate(string $key, $value, \DateTimeInterface $date): bool
542
    {
543 19
        $ttl = $date->getTimestamp() - \time();
544
545 19
        if ($ttl <= 0) {
546 4
            throw new InvalidArgumentException('Date in the past.');
547
        }
548
549 15
        return $this->setItem($key, $value, $ttl);
550
    }
551
552
    /**
553
     * Get the "isReady" state.
554
     *
555
     * @return bool
556
     */
557 6
    public function getCacheIsReady(): bool
558
    {
559 6
        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 73
    public function getPrefix(): string
600
    {
601 73
        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 89
    protected function getTheDefaultPrefix(): string
618
    {
619 89
        return ($_SERVER['SERVER_NAME'] ?? '') . '_' .
620 89
               ($_SERVER['THEME'] ?? '') . '_' .
621 89
               ($_SERVER['STAGE'] ?? '') . '_' .
622 89
               ($_SESSION['language'] ?? '') . '_' .
623 89
               ($_SESSION['language_extra'] ?? '');
624
    }
625
626
    /**
627
     * Get the current adapter class-name.
628
     *
629
     * @return string
630
     */
631 3
    public function getUsedAdapterClassName(): string
632
    {
633 3
        if ($this->adapter) {
634
            /** @noinspection GetClassUsageInspection */
635 3
            return \get_class($this->adapter);
636
        }
637
638
        return '';
639
    }
640
641
    /**
642
     * Get the current serializer class-name.
643
     *
644
     * @return string
645
     */
646 3
    public function getUsedSerializerClassName(): string
647
    {
648 3
        if ($this->serializer) {
649
            /** @noinspection GetClassUsageInspection */
650 3
            return \get_class($this->serializer);
651
        }
652
653
        return '';
654
    }
655
656
    /**
657
     * check if the current use is a admin || dev || server == client
658
     *
659
     * @return bool
660
     */
661
    public function isCacheActiveForTheCurrentUser(): bool
662
    {
663
        $active = true;
664
665
        // test the cache, with this GET-parameter
666
        if ($this->disableCacheGetParameter) {
667
            $testCache = isset($_GET[$this->disableCacheGetParameter]) ? (int) $_GET[$this->disableCacheGetParameter] : 0;
668
        } else {
669
            $testCache = 0;
670
        }
671
672
        if ($testCache !== 1) {
673
            if (
674
                // admin session is active
675
                (
676
                    $this->useCheckForAdminSession
677
                    &&
678
                    $this->isAdminSession
679
                )
680
                ||
681
                // server == client
682
                (
683
                    $this->useCheckForServerIpIsClientIp
684
                    &&
685
                    isset($_SERVER['SERVER_ADDR'])
686
                    &&
687
                    $_SERVER['SERVER_ADDR'] === $this->getClientIp()
688
                )
689
                ||
690
                // user is a dev
691
                (
692
                    $this->useCheckForDev
693
                    &&
694
                    $this->checkForDev()
695
                )
696
            ) {
697
                $active = false;
698
            }
699
        }
700
701
        return $active;
702
    }
703
704
    /**
705
     * enable / disable the cache
706
     *
707
     * @param bool $isActive
708
     */
709
    public function setActive(bool $isActive)
710
    {
711
        $this->isActive = $isActive;
712
    }
713
714
    /**
715
     * Set "isReady" state.
716
     *
717
     * @param bool $isReady
718
     */
719 89
    protected function setCacheIsReady(bool $isReady)
720
    {
721 89
        $this->isReady = $isReady;
722 89
    }
723
724
    /**
725
     * !!! Set the prefix. !!!
726
     *
727
     * WARNING: Do not use if you don't know what you do. Because this will overwrite the default prefix.
728
     *
729
     * @param string $prefix
730
     */
731 89
    public function setPrefix(string $prefix)
732
    {
733 89
        $this->prefix = $prefix;
734 89
    }
735
736
    /**
737
     * Set the static-hit-counter: Who often do we hit the cache, before we use static cache?
738
     *
739
     * @param int $staticCacheHitCounter
740
     */
741
    public function setStaticCacheHitCounter(int $staticCacheHitCounter)
742
    {
743
        $this->staticCacheHitCounter = $staticCacheHitCounter;
744
    }
745
}
746