Passed
Push — master ( caeff7...e23e7b )
by Sergei
02:02
created

MemorySimpleCache::getValues()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 8
ccs 5
cts 5
cp 1
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Test\Support\SimpleCache;
6
7
use DateInterval;
8
use DateTime;
9
use Psr\SimpleCache\CacheInterface;
10
use Traversable;
11
use Yiisoft\Test\Support\SimpleCache\Exception\InvalidArgumentException;
12
use function array_key_exists;
13
use function is_object;
14
use function is_string;
15
16
final class MemorySimpleCache implements CacheInterface
17
{
18
    protected const EXPIRATION_INFINITY = 0;
19
    protected const EXPIRATION_EXPIRED = -1;
20
21
    public bool $returnOnSet = true;
22
    public bool $returnOnDelete = true;
23
    public bool $returnOnClear = true;
24
25
    /** @var array<string, array<int, mixed>> */
26
    protected array $cache = [];
27
28 285
    public function __construct(array $cacheData = [])
29
    {
30 285
        $this->setMultiple($cacheData);
31
    }
32
33 159
    public function get($key, $default = null)
34
    {
35 159
        $this->validateKey($key);
36 143
        if (array_key_exists($key, $this->cache) && !$this->isExpired($key)) {
37
            /** @psalm-var mixed $value */
38 117
            $value = $this->cache[$key][0];
39 117
            if (is_object($value)) {
40 16
                $value = clone $value;
41
            }
42
43 117
            return $value;
44
        }
45
46 53
        return $default;
47
    }
48
49 200
    public function set($key, $value, $ttl = null): bool
50
    {
51 200
        $this->validateKey($key);
52 184
        $expiration = $this->ttlToExpiration($ttl);
53 184
        if ($expiration < 0) {
54 2
            return $this->delete($key);
55
        }
56 184
        if (is_object($value)) {
57 42
            $value = clone $value;
58
        }
59 184
        $this->cache[$key] = [$value, $expiration];
60 184
        return $this->returnOnSet;
61
    }
62
63 46
    public function delete($key): bool
64
    {
65 46
        $this->validateKey($key);
66 30
        unset($this->cache[$key]);
67 30
        return $this->returnOnDelete;
68
    }
69
70 167
    public function clear(): bool
71
    {
72 167
        $this->cache = [];
73 167
        return $this->returnOnClear;
74
    }
75
76
    /**
77
     * @param iterable $keys
78
     * @param mixed $default
79
     *
80
     * @return mixed[]
81
     */
82 30
    public function getMultiple($keys, $default = null): iterable
83
    {
84 30
        $keys = $this->iterableToArray($keys);
85 29
        $this->validateKeys($keys);
86
        /** @psalm-var string[] $keys */
87 15
        $result = [];
88 15
        foreach ($keys as $key) {
89
            /** @psalm-var mixed */
90 15
            $result[$key] = $this->get($key, $default);
91
        }
92 15
        return $result;
93
    }
94
95
    /**
96
     * @param iterable $values
97
     * @param DateInterval|int|null $ttl
98
     */
99 285
    public function setMultiple($values, $ttl = null): bool
100
    {
101 285
        $values = $this->iterableToArray($values);
102 285
        $this->validateKeysOfValues($values);
103
        /**
104
         * @psalm-var mixed $value
105
         */
106 285
        foreach ($values as $key => $value) {
107 26
            $this->set((string) $key, $value, $ttl);
108
        }
109 285
        return $this->returnOnSet;
110
    }
111
112 18
    public function deleteMultiple($keys): bool
113
    {
114 18
        $keys = $this->iterableToArray($keys);
115 17
        $this->validateKeys($keys);
116
        /** @var string[] $keys */
117 3
        foreach ($keys as $key) {
118 3
            $this->delete($key);
119
        }
120 3
        return $this->returnOnDelete;
121
    }
122
123 70
    public function has($key): bool
124
    {
125 70
        $this->validateKey($key);
126
        /** @psalm-var string $key */
127 54
        return isset($this->cache[$key]) && !$this->isExpired($key);
128
    }
129
130
    /**
131
     * Get stored data
132
     *
133
     * @return array<array-key, mixed>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>.
Loading history...
134
     */
135 1
    public function getValues(): array
136
    {
137 1
        $result = [];
138 1
        foreach ($this->cache as $key => $value) {
139
            /** @psalm-var mixed */
140 1
            $result[$key] = $value[0];
141
        }
142 1
        return $result;
143
    }
144
145
    /**
146
     * Checks whether item is expired or not
147
     */
148 133
    private function isExpired(string $key): bool
149
    {
150 133
        return $this->cache[$key][1] !== 0 && $this->cache[$key][1] <= time();
151
    }
152
153
    /**
154
     * Converts TTL to expiration
155
     *
156
     * @param DateInterval|int|null $ttl
157
     *
158
     * @return int
159
     */
160 184
    private function ttlToExpiration($ttl): int
161
    {
162 184
        $ttl = $this->normalizeTtl($ttl);
163
164 184
        if ($ttl === null) {
165 182
            $expiration = self::EXPIRATION_INFINITY;
166 6
        } elseif ($ttl <= 0) {
167 2
            $expiration = self::EXPIRATION_EXPIRED;
168
        } else {
169 4
            $expiration = $ttl + time();
170
        }
171
172 184
        return $expiration;
173
    }
174
175
    /**
176
     * Normalizes cache TTL handling strings and {@see DateInterval} objects.
177
     *
178
     * @param DateInterval|int|string|null $ttl raw TTL.
179
     *
180
     * @return int|null TTL value as UNIX timestamp or null meaning infinity
181
     */
182 184
    private function normalizeTtl($ttl): ?int
183
    {
184 184
        if ($ttl instanceof DateInterval) {
185 2
            return (new DateTime('@0'))->add($ttl)->getTimestamp();
186
        }
187
188 184
        if (is_string($ttl)) {
189
            return (int)$ttl;
190
        }
191
192 184
        return $ttl;
193
    }
194
195
    /**
196
     * @param mixed $iterable
197
     *
198
     * Converts iterable to array. If provided value is not iterable it throws an InvalidArgumentException
199
     */
200 285
    private function iterableToArray($iterable): array
201
    {
202 285
        if (!is_iterable($iterable)) {
203 3
            throw new InvalidArgumentException(sprintf('Iterable is expected, got %s.', gettype($iterable)));
204
        }
205 285
        return $iterable instanceof Traversable ? iterator_to_array($iterable) : $iterable;
206
    }
207
208
    /**
209
     * @param mixed $key
210
     */
211 278
    private function validateKey($key): void
212
    {
213 278
        if (!is_string($key) || $key === '' || strpbrk($key, '{}()/\@:')) {
214 106
            throw new InvalidArgumentException('Invalid key value.');
215
        }
216
    }
217
218
    /**
219
     * @param mixed[] $keys
220
     */
221 285
    private function validateKeys(array $keys): void
222
    {
223
        /** @psalm-var mixed $key */
224 285
        foreach ($keys as $key) {
225 68
            $this->validateKey($key);
226
        }
227
    }
228
229 285
    private function validateKeysOfValues(array $values): void
230
    {
231 285
        $keys = array_map('strval', array_keys($values));
232 285
        $this->validateKeys($keys);
233
    }
234
}
235