Passed
Pull Request — master (#16)
by Evgeniy
02:08
created

ApcuCache::validateKeysOfValues()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 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;
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
39
    public function get($key, $default = null)
40
    {
41
        $this->validateKey($key);
42
        $value = apcu_fetch($key, $success);
43
        return $success ? $value : $default;
44
    }
45
46
    public function set($key, $value, $ttl = null): bool
47
    {
48
        $this->validateKey($key);
49
        $ttl = $this->normalizeTtl($ttl);
50
51
        if ($ttl < 0) {
52
            return $this->delete($key);
53
        }
54
55
        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...
56
    }
57
58
    public function delete($key): bool
59
    {
60
        $this->validateKey($key);
61
        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...
62
    }
63
64
    public function clear(): bool
65
    {
66
        return apcu_clear_cache();
67
    }
68
69
    public function getMultiple($keys, $default = null): iterable
70
    {
71
        $keys = $this->iterableToArray($keys);
72
        $this->validateKeys($keys);
73
        $values = array_fill_keys($keys, $default);
74
75
        if (($valuesFromCache = apcu_fetch($keys)) === false) {
76
            return $values;
77
        }
78
79
        $valuesFromCache = $this->normalizeAPCuOutput($valuesFromCache);
80
81
        foreach ($values as $key => $value) {
82
            $values[$key] = $valuesFromCache[(string) $key] ?? $value;
83
        }
84
85
        return $values;
86
    }
87
88
    public function setMultiple($values, $ttl = null): bool
89
    {
90
        $ttl = $this->normalizeTtl($ttl);
91
        $values = $this->iterableToArray($values);
92
        $this->validateKeysOfValues($values);
93
        [$valuesWithStringKeys, $valuesWithIntegerKeys] = $this->splitValuesByKeyType($values);
94
95
        /** @psalm-suppress RedundantCondition */
96
        if (apcu_store($valuesWithStringKeys, null, $ttl) !== []) {
97
            return false;
98
        }
99
100
        foreach ($valuesWithIntegerKeys as $key => $value) {
101
            if (!apcu_store((string) $key, $value, $ttl)) {
102
                return false;
103
            }
104
        }
105
106
        return true;
107
    }
108
109
    public function deleteMultiple($keys): bool
110
    {
111
        $keys = $this->iterableToArray($keys);
112
        $this->validateKeys($keys);
113
        return apcu_delete($keys) === [];
114
    }
115
116
    public function has($key): bool
117
    {
118
        $this->validateKey($key);
119
        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
    private function normalizeTtl($ttl): int
130
    {
131
        if ($ttl === null) {
132
            return self::TTL_INFINITY;
133
        }
134
135
        if ($ttl instanceof DateInterval) {
136
            return (new DateTime('@0'))->add($ttl)->getTimestamp();
137
        }
138
139
        return (int) $ttl;
140
    }
141
142
    /**
143
     * Converts iterable to array. If provided value is not iterable it throws an InvalidArgumentException.
144
     *
145
     * @param mixed $iterable
146
     *
147
     * @return array
148
     */
149
    private function iterableToArray($iterable): array
150
    {
151
        if (!is_iterable($iterable)) {
152
            throw new InvalidArgumentException('Iterable is expected, got ' . gettype($iterable));
153
        }
154
155
        /** @psalm-suppress RedundantCast */
156
        return $iterable instanceof Traversable ? iterator_to_array($iterable) : (array) $iterable;
157
    }
158
159
    /**
160
     * @param mixed $key
161
     */
162
    private function validateKey($key): void
163
    {
164
        if (!is_string($key) || $key === '' || strpbrk($key, '{}()/\@:')) {
165
            throw new InvalidArgumentException('Invalid key value.');
166
        }
167
    }
168
169
    /**
170
     * @param array $keys
171
     */
172
    private function validateKeys(array $keys): void
173
    {
174
        foreach ($keys as $key) {
175
            $this->validateKey($key);
176
        }
177
    }
178
179
    /**
180
     * @param array $values
181
     */
182
    private function validateKeysOfValues(array $values): void
183
    {
184
        $keys = array_map('strval', array_keys($values));
185
        $this->validateKeys($keys);
186
    }
187
188
    /**
189
     * Normalizes keys returned from apcu_fetch in multiple mode. If one of the keys is an integer (123) or a string
190
     * representation of an integer ('123') the returned key from the cache doesn't equal neither to an integer nor a
191
     * string ($key !== 123 and $key !== '123'). Coping element from the returned array one by one to the new array
192
     * fixes this issue.
193
     *
194
     * @param array $values
195
     *
196
     * @return array
197
     */
198
    private function normalizeAPCuOutput(array $values): array
199
    {
200
        $normalizedValues = [];
201
202
        foreach ($values as $key => $value) {
203
            $normalizedValues[$key] = $value;
204
        }
205
206
        return $normalizedValues;
207
    }
208
209
    /**
210
     * Splits the array of values into two arrays, one with int keys and one with string keys
211
     *
212
     * @param array $values
213
     *
214
     * @return array
215
     */
216
    private function splitValuesByKeyType(array $values): array
217
    {
218
        $withIntKeys = [];
219
220
        foreach ($values as $key => $value) {
221
            if (is_int($key)) {
222
                $withIntKeys[$key] = $value;
223
                unset($values[$key]);
224
            }
225
        }
226
227
        return [$values, $withIntKeys];
228
    }
229
}
230