Passed
Pull Request — master (#536)
by Def
02:40
created

SchemaCache   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 178
Duplicated Lines 0 %

Test Coverage

Coverage 80.65%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 40
dl 0
loc 178
ccs 25
cts 31
cp 0.8065
rs 10
c 4
b 0
f 0
wmc 23

13 Methods

Rating   Name   Duplication   Size   Complexity  
A invalidate() 0 6 2
B normalize() 0 13 7
A isEnabled() 0 3 1
A setExclude() 0 3 1
A getDuration() 0 3 1
A remove() 0 4 1
A getOrSet() 0 23 3
A setDuration() 0 3 1
A addToTag() 0 9 2
A set() 0 8 1
A isExcluded() 0 3 1
A __construct() 0 2 1
A setEnable() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Cache;
6
7
use DateInterval;
8
use Psr\SimpleCache\CacheInterface;
9
use Yiisoft\Db\Exception\InvalidArgumentException;
10
use Yiisoft\Db\Exception\InvalidCallException;
11
12
13
/**
14
 * The SchemaCache class is used to cache database schema information.
15
 *
16
 * The Schema class retrieves information about the database schema from the database server and stores it in the cache
17
 * for faster access. When the Schema class needs to retrieve information about the database schema, it first checks the
18
 * cache using the SchemaCache class. If the information is not in the cache, the Schema class retrieves it from the
19
 * database server and stores it in the cache using the SchemaCache class.
20
 *
21 3029
 * SchemaCache is used by {@see \Yiisoft\Db\Schema\Schema} to cache table metadata.
22
 */
23 3029
final class SchemaCache
24 3029
{
25
    private bool $enabled = true;
26
    private int|null $duration = 3600;
27
    private array $exclude = [];
28
29
    public function __construct(private CacheInterface $psrCache)
30
    {
31 1567
    }
32
33 1567
    /**
34 1567
     * Remove a value with the specified key from cache.
35
     *
36 1635
     * @param mixed $key A key identifying the value to be deleted from cache.
37
     * @throws InvalidArgumentException
38 1635
     * @throws \Psr\SimpleCache\InvalidArgumentException
39 1635
     */
40 1635
    public function remove(mixed $key): void
41
    {
42
        $stringKey = $this->normalize($key);
43
        $this->psrCache->delete($stringKey);
44
    }
45
46 1567
    public function getOrSet(
47
        mixed $key,
48 1567
        mixed $value = null,
49 1567
        DateInterval|int|null $ttl = null,
50 1567
        string $cacheTag = null
51
    ): mixed {
52
        $stringKey = $this->normalize($key);
53
        if ($this->psrCache->has($stringKey)) {
54
            return $this->psrCache->get($stringKey);
55
        }
56
57 1622
        $result = $this->psrCache->set(
58
            $stringKey,
59 1622
            $value,
60
            $ttl
61
        );
62
63
        if ($result) {
64
            $this->addToTag($cacheTag, $stringKey);
65
            return $value;
66
        }
67
68
        throw new InvalidCallException('Cache value not setted');
69 1622
    }
70
71 1622
    public function set(
72
        mixed $key,
73
        mixed $value,
74
        DateInterval|int $ttl = null,
75
        string $cacheTag = null
76
    ): void {
77
        $this->remove($key);
78
        $this->getOrSet($key, $value, $ttl, $cacheTag);
79 96
    }
80
81 96
    /**
82 96
     * @return int|null The number of seconds that table metadata can remain valid in cache.
83
     */
84
    public function getDuration(): int|null
85
    {
86
        return $this->duration;
87
    }
88
89 1640
    /**
90
     * @param string $value The table name.
91 1640
     *
92
     * @return bool Whether the table is excluded from caching.
93
     */
94
    public function isExcluded(string $value): bool
95
    {
96
        return in_array($value, $this->exclude, true);
97
    }
98
99
    /**
100
     * Invalidates all the cached values that are associated with any of the specified.
101
     *
102
     * @param string $cacheTag The cache tag used to identify the values to be invalidated.
103 39
     * @throws \Psr\SimpleCache\InvalidArgumentException
104
     */
105 39
    public function invalidate(string $cacheTag): void
106 39
    {
107
        /** @var string[] $data */
108
        $data = $this->psrCache->get($cacheTag, []);
109
        foreach ($data as $key) {
110
            $this->psrCache->delete($key);
111
        }
112
    }
113
114
    /**
115
     * Return true if SchemaCache is active.
116
     */
117
    public function isEnabled(): bool
118
    {
119
        return $this->enabled;
120
    }
121
122
    /**
123
     * Whether to enable schema caching. Note that in order to enable truly schema caching, a valid cache component as
124
     * specified must be enabled and must be set true.
125
     *
126
     * @param bool $value Whether to enable schema caching.
127
     *
128
     * {@see setDuration()}
129
     * {@see setExclude()}
130
     */
131
    public function setEnable(bool $value): void
132
    {
133
        $this->enabled = $value;
134
    }
135
136
    /**
137
     * Number of seconds that table metadata can remain valid in cache. Use 'null' to indicate that the cached data will
138
     * never expire.
139
     *
140
     * @param int|null $value The number of seconds that table metadata can remain valid in cache.
141
     *
142
     * {@see setEnable()}
143
     */
144
    public function setDuration(int|null $value): void
145
    {
146
        $this->duration = $value;
147
    }
148
149
    /**
150
     * List of tables whose metadata should NOT be cached. Defaults to empty array. The table names may contain schema
151
     * prefix, if any. Do not quote the table names.
152
     *
153
     * @param array $value The table names.
154
     *
155
     * {@see setEnable()}
156
     */
157
    public function setExclude(array $value): void
158
    {
159
        $this->exclude = $value;
160
    }
161
162
    /**
163
     * Normalizes the cache key from a given key.
164
     *
165
     * If the given key is a string that does not contain characters `{}()/\@:` and no more than 64 characters,
166
     * then the key will be returned back as it is, integers will be converted to strings. Otherwise,
167
     * a normalized key is generated by serializing the given key and applying MD5 hashing.
168
     *
169
     * @see https://www.php-fig.org/psr/psr-16/#12-definitions
170
     *
171
     * @param mixed $key The key to be normalized.
172
     *
173
     * @throws InvalidArgumentException For invalid key.
174
     *
175
     * @return string The normalized cache key.
176
     */
177
    private function normalize(mixed $key): string
178
    {
179
        if (is_string($key) || is_int($key)) {
180
            $key = (string) $key;
181
            $length = mb_strlen($key, '8bit');
182
            return (strpbrk($key, '{}()/\@:') || $length < 1 || $length > 64) ? md5($key) : $key;
183
        }
184
185
        if (($key = json_encode($key)) === false) {
186
            throw new InvalidArgumentException('Invalid key. ' . json_last_error_msg());
187
        }
188
189
        return md5($key);
190
    }
191
192
    private function addToTag(string $tag = null, string $key): void
193
    {
194
        if ($tag === null) {
195
            return;
196
        }
197
        /** @var string[] $data */
198
        $data = $this->psrCache->get($tag, []);
199
        $data[] = $key;
200
        $this->psrCache->set($tag, $data);
201
    }
202
}
203