SchemaCache::setExclude()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
c 1
b 0
f 0
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 Psr\SimpleCache\InvalidArgumentException;
10
use RuntimeException;
11
use Yiisoft\Db\Exception\PsrInvalidArgumentException;
12
13
use function in_array;
14
use function is_int;
15
use function is_string;
16
use function json_encode;
17
use function json_last_error_msg;
18
use function mb_strlen;
19
use function md5;
20
use function strpbrk;
21 3029
22
/**
23 3029
 * Implements a cache for the database schema information.
24 3029
 *
25
 * The {@see \Yiisoft\Db\Schema\AbstractSchema} retrieves information about the database schema from the database server
26
 * and stores it in the cache for faster access. When the {@see \Yiisoft\Db\Schema\AbstractSchema} needs to retrieve
27
 * information about the database schema, it first checks the cache using {@see SchemaCache}. If the information is
28
 * not in the cache, the Schema retrieves it from the database server and stores it in the cache using the
29
 * {@see SchemaCache}.
30
 *
31 1567
 * {@see \Yiisoft\Db\Schema\AbstractSchema} uses this implementation to cache table metadata.
32
 */
33 1567
final class SchemaCache
34 1567
{
35
    private int|null|DateInterval $duration = 3600;
36 1635
    private bool $enabled = true;
37
    private array $exclude = [];
38 1635
39 1635
    /**
40 1635
     * @param CacheInterface $psrCache PSR-16 cache implementation to use.
41
     *
42
     * @link https://www.php-fig.org/psr/psr-16/
43
     */
44
    public function __construct(private CacheInterface $psrCache)
45
    {
46 1567
    }
47
48 1567
    /**
49 1567
     * Remove a value with the specified key from cache.
50 1567
     *
51
     * @param mixed $key A key identifying the value to delete from cache.
52
     *
53
     * @throws InvalidArgumentException
54
     */
55
    public function remove(mixed $key): void
56
    {
57 1622
        $stringKey = $this->normalize($key);
58
        $this->psrCache->delete($stringKey);
59 1622
    }
60
61
    /**
62
     * Retrieve value from cache.
63
     *
64
     * @param mixed $key The key identifying the value to cache.
65
     * @throws InvalidArgumentException
66
     * @return mixed Cache value.
67
     */
68
    public function get(mixed $key): mixed
69 1622
    {
70
        $stringKey = $this->normalize($key);
71 1622
        return $this->psrCache->get($stringKey);
72
    }
73
74
    /**
75
     * Persists data in the cache, uniquely referenced by a key with an optional tag.
76
     *
77
     * @param mixed $key The key of the item to store.
78
     * @param mixed $value The value of the item to store.
79 96
     * @param string|null $tag Cache tag.
80
     *
81 96
     * @throws InvalidArgumentException If the $key string isn't a legal value.
82 96
     * @throws RuntimeException If cache value isn't set.
83
     */
84
    public function set(mixed $key, mixed $value, string $tag = null): void
85
    {
86
        $stringKey = $this->normalize($key);
87
88
        if ($this->psrCache->set($stringKey, $value, $this->duration)) {
89 1640
            $this->addToTag($stringKey, $tag);
90
            return;
91 1640
        }
92
93
        throw new RuntimeException('Cache value not set.');
94
    }
95
96
    /**
97
     * @return DateInterval|int|null The number of seconds that table metadata can remain valid in cache.
98
     */
99
    public function getDuration(): int|null|DateInterval
100
    {
101
        return $this->duration;
102
    }
103 39
104
    /**
105 39
     * @param string $value The table name.
106 39
     *
107
     * @return bool Whether to exclude the table from caching.
108
     */
109
    public function isExcluded(string $value): bool
110
    {
111
        return in_array($value, $this->exclude, true);
112
    }
113
114
    /**
115
     * Invalidates all the cached values associated with any of the specified tags.
116
     *
117
     * @param string $cacheTag The cache tag used to identify the values to invalidate.
118
     *
119
     * @throws InvalidArgumentException
120
     */
121
    public function invalidate(string $cacheTag): void
122
    {
123
        if (empty($cacheTag)) {
124
            return;
125
        }
126
127
        /** @psalm-var string[] $data */
128
        $data = $this->psrCache->get($cacheTag, []);
129
130
        foreach ($data as $key) {
131
            $this->psrCache->delete($key);
132
        }
133
    }
134
135
    /**
136
     * Return true if SchemaCache is active.
137
     */
138
    public function isEnabled(): bool
139
    {
140
        return $this->enabled;
141
    }
142
143
    /**
144
     * Whether to enable schema caching.
145
     *
146
     * @param bool $value Whether to enable schema caching.
147
     *
148
     * @see setDuration()
149
     * @see setExclude()
150
     */
151
    public function setEnabled(bool $value): void
152
    {
153
        $this->enabled = $value;
154
    }
155
156
    /**
157
     * Number of seconds that table metadata can remain valid in cache. Use 'null' to indicate that the cached data will
158
     * never expire.
159
     *
160
     * @param DateInterval|int|null $value The number of seconds that table metadata can remain valid in cache.
161
     *
162
     * @see setEnabled()
163
     */
164
    public function setDuration(int|null|DateInterval $value): void
165
    {
166
        $this->duration = $value;
167
    }
168
169
    /**
170
     * List of tables not to cache metadata for.
171
     *
172
     * Defaults to an empty array. The table names may contain schema prefix, if any. Don't quote the table names.
173
     *
174
     * @param array $value The table names.
175
     *
176
     * @see setEnabled()
177
     */
178
    public function setExclude(array $value): void
179
    {
180
        $this->exclude = $value;
181
    }
182
183
    /**
184
     * Normalizes the cache key from a given key.
185
     *
186
     * If the given key is a string that doesn't contain characters `{}()/\@:` and no more than 64 characters, then the
187
     * key will be returned back as it's, integers will be converted to strings.
188
     *
189
     * Otherwise, a normalized key is generated by encoding the given key into JSON and applying MD5 hashing.
190
     *
191
     * @link https://www.php-fig.org/psr/psr-16/#12-definitions
192
     *
193
     * @param mixed $key A key to normalize.
194
     *
195
     * @throws InvalidArgumentException For invalid key.
196
     *
197
     * @return string The normalized cache key.
198
     */
199
    private function normalize(mixed $key): string
200
    {
201
        if (is_string($key) || is_int($key)) {
202
            $key = (string)$key;
203
            $length = mb_strlen($key, '8bit');
204
            return (strpbrk($key, '{}()/\@:') !== false || $length < 1 || $length > 64) ? md5($key) : $key;
205
        }
206
207
        $key = json_encode($key);
208
209
        if ($key === false) {
210
            throw new PsrInvalidArgumentException('Invalid key. ' . json_last_error_msg());
211
        }
212
213
        return md5($key);
214
    }
215
216
    /**
217
     * Add key to tag. If tag is empty, do nothing.
218
     *
219
     * @throws InvalidArgumentException
220
     */
221
    private function addToTag(string $key, string $cacheTag = null): void
222
    {
223
        if (empty($cacheTag)) {
224
            return;
225
        }
226
227
        /** @psalm-var string[] $data */
228
        $data = $this->psrCache->get($cacheTag, []);
229
        $data[] = $key;
230
        $this->psrCache->set($cacheTag, $data);
231
    }
232
}
233