Passed
Pull Request — master (#6)
by Alexander
01:55
created

ApcuCache::iterableToArray()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 10
cc 3
nc 3
nop 1
crap 3
1
<?php declare(strict_types=1);
2
3
namespace Yiisoft\Cache\Apcu;
4
5
use DateInterval;
6
use DateTime;
7
use Psr\SimpleCache\CacheInterface;
8
9
/**
10
 * ApcuCache provides APCu caching in terms of an application component.
11
 *
12
 * To use this application component, the [APCu PHP extension](http://www.php.net/apcu) must be loaded.
13
 * In order to enable APCu for CLI you should add "apc.enable_cli = 1" to your php.ini.
14
 *
15
 * See {@see \Psr\SimpleCache\CacheInterface} for common cache operations that ApcCache supports.
16
 */
17
class ApcuCache implements CacheInterface
18
{
19
    private const TTL_INFINITY = 0;
20
21 65
    public function get($key, $default = null)
22
    {
23 65
        $this->validateKey($key);
24 64
        $value = \apcu_fetch($key, $success);
25 64
        return $success ? $value : $default;
26
    }
27
28 75
    public function set($key, $value, $ttl = null): bool
29
    {
30 75
        $this->validateKey($key);
31 74
        $ttl = $this->normalizeTtl($ttl);
32 74
        if ($ttl < 0) {
33 1
            return $this->delete($key);
34
        }
35 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...
36
    }
37
38 14
    public function delete($key): bool
39
    {
40 14
        $this->validateKey($key);
41 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...
42
    }
43
44 83
    public function clear(): bool
45
    {
46 83
        return \apcu_clear_cache();
47
    }
48
49 9
    public function getMultiple($keys, $default = null): iterable
50
    {
51 9
        $keys = $this->iterableToArray($keys);
52 8
        $this->validateKeys($keys);
53 7
        $valuesFromCache = \apcu_fetch($keys, $success) ?: [];
54 7
        $valuesFromCache = $this->normalizeAPCUoutput($valuesFromCache);
55 7
        $values = array_fill_keys($keys, $default);
56 7
        foreach ($values as $key => $value) {
57 7
            $values[$key] = $valuesFromCache[(string)$key] ?? $value;
58
        }
59
60 7
        return $values;
61
    }
62
63 11
    public function setMultiple($values, $ttl = null): bool
64
    {
65 11
        $values = $this->iterableToArray($values);
66 10
        $this->validateKeysOfValues($values);
67 10
        [$valuesWithStringKeys, $valuesWithIntegerKeys] = $this->splitValuesByKeyType($values);
68 10
        $ttl = $this->normalizeTtl($ttl);
69 10
        $result = \apcu_store($valuesWithStringKeys, null, $ttl) === [];
70 10
        foreach ($valuesWithIntegerKeys as $key => $value) {
71 4
            $result = $result && \apcu_store((string)$key, $value, $ttl);
72
        }
73
74 10
        return $result;
75
    }
76
77 3
    public function deleteMultiple($keys): bool
78
    {
79 3
        $keys = $this->iterableToArray($keys);
80 2
        $this->validateKeys($keys);
81 1
        return \apcu_delete($keys) === [];
82
    }
83
84 14
    public function has($key): bool
85
    {
86 14
        $this->validateKey($key);
87 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...
88
    }
89
90
    /**
91
     * @noinspection PhpDocMissingThrowsInspection DateTime won't throw exception because constant string is passed as time
92
     *
93
     * Normalizes cache TTL handling `null` value, strings and {@see DateInterval} objects.
94
     * @param int|string|DateInterval|null $ttl raw TTL.
95
     * @return int TTL value as UNIX timestamp
96
     */
97 89
    private function normalizeTtl($ttl): ?int
98
    {
99 89
        $normalizedTtl = $ttl;
100 89
        if ($ttl instanceof DateInterval) {
101 3
            $normalizedTtl = (new DateTime('@0'))->add($ttl)->getTimestamp();
102
        }
103
104 89
        if (is_string($normalizedTtl)) {
105 2
            $normalizedTtl = (int)$normalizedTtl;
106
        }
107
108 89
        return $normalizedTtl ?? static::TTL_INFINITY;
109
    }
110
111
    /**
112
     * Converts iterable to array. If provided value is not iterable it throws an InvalidArgumentException
113
     * @param $iterable
114
     * @return array
115
     */
116 15
    private function iterableToArray($iterable): array
117
    {
118 15
        if (!is_iterable($iterable)) {
119 3
            throw new InvalidArgumentException('Iterable is expected, got ' . gettype($iterable));
120
        }
121
122 12
        return $iterable instanceof \Traversable ? iterator_to_array($iterable) : (array)$iterable;
123
    }
124
125
    /**
126
     * @param $key
127
     */
128 89
    private function validateKey($key): void
129
    {
130 89
        if (!\is_string($key)) {
131 6
            throw new InvalidArgumentException('Invalid key value.');
132
        }
133 83
    }
134
135
    /**
136
     * @param array $keys
137
     */
138 12
    private function validateKeys(array $keys): void
139
    {
140 12
        foreach ($keys as $key) {
141 12
            $this->validateKey($key);
142
        }
143 10
    }
144
145
    /**
146
     * @param array $values
147
     */
148 10
    private function validateKeysOfValues(array $values): void
149
    {
150 10
        $keys = array_map('strval', array_keys($values));
151 10
        $this->validateKeys($keys);
152 10
    }
153
154
    /**
155
     * Normalizes keys returned from apcu_fetch in multiple mode. If one of the keys is an integer (123) or a string
156
     * representation of an integer ('123') the returned key from the cache doesn't equal neither to an integer nor a
157
     * string ($key !== 123 and $key !== '123'). Coping element from the returned array one by one to the new array
158
     * fixes this issue.
159
     * @param array $values
160
     * @return array
161
     */
162 7
    private function normalizeAPCUoutput(array $values): array
163
    {
164 7
        $normalizedValues = [];
165 7
        foreach ($values as $key => $value) {
166 7
            $normalizedValues[$key] = $value;
167
        }
168
169 7
        return $normalizedValues;
170
    }
171
172
    /**
173
     * Splits the array of values into two arrays, one with int keys and one with string keys
174
     * @param array $values
175
     * @return array
176
     */
177 10
    private function splitValuesByKeyType(array $values): array
178
    {
179 10
        $withIntKeys = [];
180 10
        foreach ($values as $key => $value) {
181 10
            if (\is_int($key)) {
182 4
                $withIntKeys[$key] = $value;
183 4
                unset($values[$key]);
184
            }
185
        }
186
187 10
        return [$values, $withIntKeys];
188
    }
189
}
190