Passed
Pull Request — master (#18)
by Evgeniy
02:15
created

ApcuCache   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 195
Duplicated Lines 0 %

Test Coverage

Coverage 96.15%

Importance

Changes 0
Metric Value
wmc 34
eloc 62
dl 0
loc 195
ccs 75
cts 78
cp 0.9615
rs 9.68
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A delete() 0 4 1
A validateKeys() 0 4 2
A set() 0 10 2
A clear() 0 3 1
A iterableToArray() 0 8 3
A validateKey() 0 4 4
A validateKeysOfValues() 0 4 1
A splitValuesByKeyType() 0 12 3
A has() 0 4 1
A setMultiple() 0 19 4
A normalizeAPCuOutput() 0 9 2
A normalizeTtl() 0 12 4
A getMultiple() 0 17 3
A get() 0 5 2
A deleteMultiple() 0 5 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Cache\Apcu;
6
7
use DateInterval;
8
use DateTime;
9
use Traversable;
10
use Psr\SimpleCache\CacheInterface;
0 ignored issues
show
Bug introduced by
The type Psr\SimpleCache\CacheInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
11
12
use function apcu_delete;
13
use function apcu_clear_cache;
14
use function apcu_exists;
15
use function apcu_fetch;
16
use function apcu_store;
17
use function array_fill_keys;
18
use function array_keys;
19
use function array_map;
20
use function gettype;
21
use function is_int;
22
use function is_iterable;
23
use function is_string;
24
use function iterator_to_array;
25
use function strpbrk;
26
27
/**
28
 * ApcuCache provides APCu caching in terms of an application component.
29
 *
30
 * To use this application component, the [APCu PHP extension](http://www.php.net/apcu) must be loaded.
31
 * In order to enable APCu for CLI you should add "apc.enable_cli = 1" to your php.ini.
32
 *
33
 * See {@see \Psr\SimpleCache\CacheInterface} for common cache operations that ApcCache supports.
34
 */
35
final class ApcuCache implements CacheInterface
36
{
37
    private const TTL_INFINITY = 0;
38
    private const TTL_EXPIRED = -1;
39
40 72
    public function get($key, $default = null)
41
    {
42 72
        $this->validateKey($key);
43 64
        $value = apcu_fetch($key, $success);
44 64
        return $success ? $value : $default;
45
    }
46
47 82
    public function set($key, $value, $ttl = null): bool
48
    {
49 82
        $this->validateKey($key);
50 74
        $ttl = $this->normalizeTtl($ttl);
51
52 74
        if ($ttl < 0) {
53 1
            return $this->delete($key);
54
        }
55
56 73
        return apcu_store($key, $value, $ttl);
0 ignored issues
show
Bug Best Practice introduced by
The expression return apcu_store($key, $value, $ttl) could return the type array which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
57
    }
58
59 21
    public function delete($key): bool
60
    {
61 21
        $this->validateKey($key);
62 13
        return apcu_delete($key);
0 ignored issues
show
Bug Best Practice introduced by
The expression return apcu_delete($key) could return the type string[] which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
63
    }
64
65 12
    public function clear(): bool
66
    {
67 12
        return apcu_clear_cache();
68
    }
69
70 24
    public function getMultiple($keys, $default = null): iterable
71
    {
72 24
        $keys = $this->iterableToArray($keys);
73 16
        $this->validateKeys($keys);
74 8
        $values = array_fill_keys($keys, $default);
75
76 8
        if (($valuesFromCache = apcu_fetch($keys)) === false) {
77
            return $values;
78
        }
79
80 8
        $valuesFromCache = $this->normalizeAPCuOutput($valuesFromCache);
81
82 8
        foreach ($values as $key => $value) {
83 8
            $values[$key] = $valuesFromCache[(string) $key] ?? $value;
84
        }
85
86 8
        return $values;
87
    }
88
89 18
    public function setMultiple($values, $ttl = null): bool
90
    {
91 18
        $ttl = $this->normalizeTtl($ttl);
92 18
        $values = $this->iterableToArray($values);
93 10
        $this->validateKeysOfValues($values);
94 10
        [$valuesWithStringKeys, $valuesWithIntegerKeys] = $this->splitValuesByKeyType($values);
95
96
        /** @psalm-suppress RedundantCondition */
97 10
        if (apcu_store($valuesWithStringKeys, null, $ttl) !== []) {
98
            return false;
99
        }
100
101 10
        foreach ($valuesWithIntegerKeys as $key => $value) {
102 4
            if (!apcu_store((string) $key, $value, $ttl)) {
103
                return false;
104
            }
105
        }
106
107 10
        return true;
108
    }
109
110 17
    public function deleteMultiple($keys): bool
111
    {
112 17
        $keys = $this->iterableToArray($keys);
113 9
        $this->validateKeys($keys);
114 1
        return apcu_delete($keys) === [];
115
    }
116
117 13
    public function has($key): bool
118
    {
119 13
        $this->validateKey($key);
120 13
        return apcu_exists($key);
0 ignored issues
show
Bug Best Practice introduced by
The expression return apcu_exists($key) could return the type string[] which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
121
    }
122
123
    /**
124
     * Normalizes cache TTL handling `null` value, strings and {@see DateInterval} objects.
125
     *
126
     * @param DateInterval|int|string|null $ttl The raw TTL.
127
     *
128
     * @return int TTL value as UNIX timestamp.
129
     */
130 97
    private function normalizeTtl($ttl): int
131
    {
132 97
        if ($ttl === null) {
133 90
            return self::TTL_INFINITY;
134
        }
135
136 9
        if ($ttl instanceof DateInterval) {
137 3
            return (new DateTime('@0'))->add($ttl)->getTimestamp();
138
        }
139
140 6
        $ttl = (int) $ttl;
141 6
        return $ttl > 0 ? $ttl : self::TTL_EXPIRED;
142
    }
143
144
    /**
145
     * Converts iterable to array. If provided value is not iterable it throws an InvalidArgumentException.
146
     *
147
     * @param mixed $iterable
148
     *
149
     * @return array
150
     */
151 51
    private function iterableToArray($iterable): array
152
    {
153 51
        if (!is_iterable($iterable)) {
154 24
            throw new InvalidArgumentException('Iterable is expected, got ' . gettype($iterable));
155
        }
156
157
        /** @psalm-suppress RedundantCast */
158 27
        return $iterable instanceof Traversable ? iterator_to_array($iterable) : (array) $iterable;
159
    }
160
161
    /**
162
     * @param mixed $key
163
     */
164 124
    private function validateKey($key): void
165
    {
166 124
        if (!is_string($key) || $key === '' || strpbrk($key, '{}()/\@:')) {
167 40
            throw new InvalidArgumentException('Invalid key value.');
168
        }
169 84
    }
170
171
    /**
172
     * @param array $keys
173
     */
174 27
    private function validateKeys(array $keys): void
175
    {
176 27
        foreach ($keys as $key) {
177 27
            $this->validateKey($key);
178
        }
179 11
    }
180
181
    /**
182
     * @param array $values
183
     */
184 10
    private function validateKeysOfValues(array $values): void
185
    {
186 10
        $keys = array_map('strval', array_keys($values));
187 10
        $this->validateKeys($keys);
188 10
    }
189
190
    /**
191
     * Normalizes keys returned from apcu_fetch in multiple mode. If one of the keys is an integer (123) or a string
192
     * representation of an integer ('123') the returned key from the cache doesn't equal neither to an integer nor a
193
     * string ($key !== 123 and $key !== '123'). Coping element from the returned array one by one to the new array
194
     * fixes this issue.
195
     *
196
     * @param array $values
197
     *
198
     * @return array
199
     */
200 8
    private function normalizeAPCuOutput(array $values): array
201
    {
202 8
        $normalizedValues = [];
203
204 8
        foreach ($values as $key => $value) {
205 7
            $normalizedValues[$key] = $value;
206
        }
207
208 8
        return $normalizedValues;
209
    }
210
211
    /**
212
     * Splits the array of values into two arrays, one with int keys and one with string keys
213
     *
214
     * @param array $values
215
     *
216
     * @return array
217
     */
218 10
    private function splitValuesByKeyType(array $values): array
219
    {
220 10
        $withIntKeys = [];
221
222 10
        foreach ($values as $key => $value) {
223 10
            if (is_int($key)) {
224 4
                $withIntKeys[$key] = $value;
225 4
                unset($values[$key]);
226
            }
227
        }
228
229 10
        return [$values, $withIntKeys];
230
    }
231
}
232