ApcuCache   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 190
Duplicated Lines 0 %

Test Coverage

Coverage 95.89%

Importance

Changes 0
Metric Value
wmc 32
eloc 60
dl 0
loc 190
ccs 70
cts 73
cp 0.9589
rs 9.84
c 0
b 0
f 0

15 Methods

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