Passed
Push — master ( 87b759...438279 )
by Sergei
02:19
created

MemorySimpleCache   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 193
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 70
c 4
b 0
f 0
dl 0
loc 193
ccs 80
cts 80
cp 1
rs 9.52
wmc 36

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getMultiple() 0 11 2
A has() 0 5 2
A set() 0 12 3
A clear() 0 4 1
A isExpired() 0 3 2
A getValues() 0 8 2
A delete() 0 5 1
A deleteMultiple() 0 9 2
A get() 0 14 4
A setMultiple() 0 11 2
A __construct() 0 3 1
A validateKeysOfValues() 0 4 1
A iterableToArray() 0 3 2
A validateKeys() 0 5 2
A validateKey() 0 4 4
A ttlToExpiration() 0 13 3
A normalizeTtl() 0 9 2
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
use function array_key_exists;
14
use function is_object;
15
use function is_string;
16
17
final class MemorySimpleCache implements CacheInterface
18
{
19
    protected const EXPIRATION_INFINITY = 0;
20
    protected const EXPIRATION_EXPIRED = -1;
21
22
    public bool $returnOnSet = true;
23
    public bool $returnOnDelete = true;
24
    public bool $returnOnClear = true;
25
26
    /** @var array<string, array<int, mixed>> */
27
    protected array $cache = [];
28
29 271
    public function __construct(array $cacheData = [])
30
    {
31 271
        $this->setMultiple($cacheData);
32
    }
33
34 157
    public function get(string $key, mixed $default = null): mixed
35
    {
36 157
        $this->validateKey($key);
37 143
        if (array_key_exists($key, $this->cache) && !$this->isExpired($key)) {
38
            /** @psalm-var mixed $value */
39 117
            $value = $this->cache[$key][0];
40 117
            if (is_object($value)) {
41 16
                $value = clone $value;
42
            }
43
44 117
            return $value;
45
        }
46
47 53
        return $default;
48
    }
49
50 198
    public function set(string $key, mixed $value, null|int|DateInterval $ttl = null): bool
51
    {
52 198
        $this->validateKey($key);
53 184
        $expiration = $this->ttlToExpiration($ttl);
54 184
        if ($expiration < 0) {
55 2
            return $this->delete($key);
56
        }
57 184
        if (is_object($value)) {
58 42
            $value = clone $value;
59
        }
60 184
        $this->cache[$key] = [$value, $expiration];
61 184
        return $this->returnOnSet;
62
    }
63
64 44
    public function delete(string $key): bool
65
    {
66 44
        $this->validateKey($key);
67 30
        unset($this->cache[$key]);
68 30
        return $this->returnOnDelete;
69
    }
70
71 167
    public function clear(): bool
72
    {
73 167
        $this->cache = [];
74 167
        return $this->returnOnClear;
75
    }
76
77 29
    public function getMultiple(iterable $keys, mixed $default = null): iterable
78
    {
79 29
        $keys = $this->iterableToArray($keys);
80 29
        $this->validateKeys($keys);
81
        /** @psalm-var string[] $keys */
82 15
        $result = [];
83 15
        foreach ($keys as $key) {
84
            /** @psalm-var mixed */
85 15
            $result[$key] = $this->get($key, $default);
86
        }
87 15
        return $result;
88
    }
89
90 271
    public function setMultiple(iterable $values, null|int|DateInterval $ttl = null): bool
91
    {
92 271
        $values = $this->iterableToArray($values);
93 271
        $this->validateKeysOfValues($values);
94
        /**
95
         * @psalm-var mixed $value
96
         */
97 271
        foreach ($values as $key => $value) {
98 26
            $this->set((string) $key, $value, $ttl);
99
        }
100 271
        return $this->returnOnSet;
101
    }
102
103 17
    public function deleteMultiple(iterable $keys): bool
104
    {
105 17
        $keys = $this->iterableToArray($keys);
106 17
        $this->validateKeys($keys);
107
        /** @var string[] $keys */
108 3
        foreach ($keys as $key) {
109 3
            $this->delete($key);
110
        }
111 3
        return $this->returnOnDelete;
112
    }
113
114 68
    public function has(string $key): bool
115
    {
116 68
        $this->validateKey($key);
117
        /** @psalm-var string $key */
118 54
        return isset($this->cache[$key]) && !$this->isExpired($key);
119
    }
120
121
    /**
122
     * Get stored data
123
     *
124
     * @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...
125
     */
126 1
    public function getValues(): array
127
    {
128 1
        $result = [];
129 1
        foreach ($this->cache as $key => $value) {
130
            /** @psalm-var mixed */
131 1
            $result[$key] = $value[0];
132
        }
133 1
        return $result;
134
    }
135
136
    /**
137
     * Checks whether item is expired or not
138
     */
139 133
    private function isExpired(string $key): bool
140
    {
141 133
        return $this->cache[$key][1] !== 0 && $this->cache[$key][1] <= time();
142
    }
143
144
    /**
145
     * Converts TTL to expiration.
146
     */
147 184
    private function ttlToExpiration(null|int|DateInterval $ttl): int
148
    {
149 184
        $ttl = $this->normalizeTtl($ttl);
150
151 184
        if ($ttl === null) {
152 182
            $expiration = self::EXPIRATION_INFINITY;
153 6
        } elseif ($ttl <= 0) {
154 2
            $expiration = self::EXPIRATION_EXPIRED;
155
        } else {
156 4
            $expiration = $ttl + time();
157
        }
158
159 184
        return $expiration;
160
    }
161
162
    /**
163
     * Normalizes cache TTL handling strings and {@see DateInterval} objects.
164
     *
165
     * @param DateInterval|int|null $ttl Raw TTL.
166
     *
167
     * @return int|null TTL value as UNIX timestamp or null meaning infinity.
168
     */
169 184
    private function normalizeTtl(null|int|DateInterval $ttl): ?int
170
    {
171 184
        if ($ttl instanceof DateInterval) {
172 2
            return (new DateTime('@0'))
173 2
                ->add($ttl)
174 2
                ->getTimestamp();
175
        }
176
177 184
        return $ttl;
178
    }
179
180
    /**
181
     * Converts iterable to array.
182
     */
183 271
    private function iterableToArray(iterable $iterable): array
184
    {
185 271
        return $iterable instanceof Traversable ? iterator_to_array($iterable) : $iterable;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $iterable instanc...($iterable) : $iterable could return the type iterable which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
186
    }
187
188 270
    private function validateKey(mixed $key): void
189
    {
190 270
        if (!is_string($key) || $key === '' || strpbrk($key, '{}()/\@:')) {
191 98
            throw new InvalidArgumentException('Invalid key value.');
192
        }
193
    }
194
195
    /**
196
     * @param mixed[] $keys
197
     */
198 271
    private function validateKeys(array $keys): void
199
    {
200
        /** @psalm-var mixed $key */
201 271
        foreach ($keys as $key) {
202 68
            $this->validateKey($key);
203
        }
204
    }
205
206 271
    private function validateKeysOfValues(array $values): void
207
    {
208 271
        $keys = array_map('strval', array_keys($values));
209 271
        $this->validateKeys($keys);
210
    }
211
}
212