Passed
Pull Request — master (#16)
by Aleksei
02:20
created

MemorySimpleCache::validateKeysOfValues()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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