MemorySimpleCache   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 186
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 186
ccs 80
cts 80
cp 1
rs 9.52
wmc 36

17 Methods

Rating   Name   Duplication   Size   Complexity  
A clear() 0 4 1
A delete() 0 5 1
A set() 0 12 3
A __construct() 0 3 1
A getMultiple() 0 10 2
A has() 0 5 2
A validateKeysOfValues() 0 4 1
A iterableToArray() 0 3 2
A isExpired() 0 3 2
A getValues() 0 7 2
A validateKeys() 0 4 2
A deleteMultiple() 0 9 2
A get() 0 13 4
A validateKey() 0 4 4
A setMultiple() 0 8 2
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 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 198
    public function set(string $key, mixed $value, null|int|DateInterval $ttl = null): bool
50
    {
51 198
        $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 44
    public function delete(string $key): bool
64
    {
65 44
        $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 29
    public function getMultiple(iterable $keys, mixed $default = null): iterable
77
    {
78 29
        $keys = $this->iterableToArray($keys);
79 29
        $this->validateKeys($keys);
80
        /** @psalm-var string[] $keys */
81 15
        $result = [];
82 15
        foreach ($keys as $key) {
83 15
            $result[$key] = $this->get($key, $default);
84
        }
85 15
        return $result;
86
    }
87
88 271
    public function setMultiple(iterable $values, null|int|DateInterval $ttl = null): bool
89
    {
90 271
        $values = $this->iterableToArray($values);
91 271
        $this->validateKeysOfValues($values);
92 271
        foreach ($values as $key => $value) {
93 26
            $this->set((string) $key, $value, $ttl);
94
        }
95 271
        return $this->returnOnSet;
96
    }
97
98 17
    public function deleteMultiple(iterable $keys): bool
99
    {
100 17
        $keys = $this->iterableToArray($keys);
101 17
        $this->validateKeys($keys);
102
        /** @var string[] $keys */
103 3
        foreach ($keys as $key) {
104 3
            $this->delete($key);
105
        }
106 3
        return $this->returnOnDelete;
107
    }
108
109 68
    public function has(string $key): bool
110
    {
111 68
        $this->validateKey($key);
112
        /** @psalm-var string $key */
113 54
        return isset($this->cache[$key]) && !$this->isExpired($key);
114
    }
115
116
    /**
117
     * Get stored data
118
     *
119
     * @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...
120
     */
121 1
    public function getValues(): array
122
    {
123 1
        $result = [];
124 1
        foreach ($this->cache as $key => $value) {
125 1
            $result[$key] = $value[0];
126
        }
127 1
        return $result;
128
    }
129
130
    /**
131
     * Checks whether item is expired or not
132
     */
133 133
    private function isExpired(string $key): bool
134
    {
135 133
        return $this->cache[$key][1] !== 0 && $this->cache[$key][1] <= time();
136
    }
137
138
    /**
139
     * Converts TTL to expiration.
140
     */
141 184
    private function ttlToExpiration(null|int|DateInterval $ttl): int
142
    {
143 184
        $ttl = $this->normalizeTtl($ttl);
144
145 184
        if ($ttl === null) {
146 182
            $expiration = self::EXPIRATION_INFINITY;
147 6
        } elseif ($ttl <= 0) {
148 2
            $expiration = self::EXPIRATION_EXPIRED;
149
        } else {
150 4
            $expiration = $ttl + time();
151
        }
152
153 184
        return $expiration;
154
    }
155
156
    /**
157
     * Normalizes cache TTL handling strings and {@see DateInterval} objects.
158
     *
159
     * @param DateInterval|int|null $ttl Raw TTL.
160
     *
161
     * @return int|null TTL value as UNIX timestamp or null meaning infinity.
162
     */
163 184
    private function normalizeTtl(null|int|DateInterval $ttl): ?int
164
    {
165 184
        if ($ttl instanceof DateInterval) {
166 2
            return (new DateTime('@0'))
167 2
                ->add($ttl)
168 2
                ->getTimestamp();
169
        }
170
171 184
        return $ttl;
172
    }
173
174
    /**
175
     * Converts iterable to array.
176
     */
177 271
    private function iterableToArray(iterable $iterable): array
178
    {
179 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...
180
    }
181
182 270
    private function validateKey(mixed $key): void
183
    {
184 270
        if (!is_string($key) || $key === '' || strpbrk($key, '{}()/\@:')) {
185 98
            throw new InvalidArgumentException('Invalid key value.');
186
        }
187
    }
188
189
    /**
190
     * @param mixed[] $keys
191
     */
192 271
    private function validateKeys(array $keys): void
193
    {
194 271
        foreach ($keys as $key) {
195 68
            $this->validateKey($key);
196
        }
197
    }
198
199 271
    private function validateKeysOfValues(array $values): void
200
    {
201 271
        $keys = array_map('strval', array_keys($values));
202 271
        $this->validateKeys($keys);
203
    }
204
}
205