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

SchemaCache::addToTag()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 2
dl 0
loc 9
ccs 0
cts 0
cp 0
crap 6
rs 10
c 0
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 Yiisoft\Db\Exception\InvalidArgumentException;
10
use Yiisoft\Db\Exception\InvalidCallException;
11
12
/**
13
 * The SchemaCache class is used to cache database schema information.
14
 *
15
 * The Schema class retrieves information about the database schema from the database server and stores it in the cache
16
 * for faster access. When the Schema class needs to retrieve information about the database schema, it first checks the
17
 * cache using the SchemaCache class. If the information is not in the cache, the Schema class retrieves it from the
18
 * database server and stores it in the cache using the SchemaCache class.
19
 *
20
 * SchemaCache is used by {@see \Yiisoft\Db\Schema\Schema} to cache table metadata.
21 3029
 */
22
final class SchemaCache
23 3029
{
24 3029
    private bool $enabled = true;
25
    private int|null $duration = 3600;
26
    private array $exclude = [];
27
28
    public function __construct(private CacheInterface $psrCache)
29
    {
30
    }
31 1567
32
    /**
33 1567
     * Remove a value with the specified key from cache.
34 1567
     *
35
     * @param mixed $key A key identifying the value to be deleted from cache.
36 1635
     *
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($stringKey, $cacheTag);
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
     *
104
     * @throws \Psr\SimpleCache\InvalidArgumentException
105 39
     */
106 39
    public function invalidate(string $cacheTag): void
107
    {
108
        if (empty($cacheTag)) {
109
            return;
110
        }
111
112
        /** @var string[] $data */
113
        $data = $this->psrCache->get($cacheTag, []);
114
        foreach ($data as $key) {
115
            $this->psrCache->delete($key);
116
        }
117
    }
118
119
    /**
120
     * Return true if SchemaCache is active.
121
     */
122
    public function isEnabled(): bool
123
    {
124
        return $this->enabled;
125
    }
126
127
    /**
128
     * Whether to enable schema caching. Note that in order to enable truly schema caching, a valid cache component as
129
     * specified must be enabled and must be set true.
130
     *
131
     * @param bool $value Whether to enable schema caching.
132
     *
133
     * {@see setDuration()}
134
     * {@see setExclude()}
135
     */
136
    public function setEnable(bool $value): void
137
    {
138
        $this->enabled = $value;
139
    }
140
141
    /**
142
     * Number of seconds that table metadata can remain valid in cache. Use 'null' to indicate that the cached data will
143
     * never expire.
144
     *
145
     * @param int|null $value The number of seconds that table metadata can remain valid in cache.
146
     *
147
     * {@see setEnable()}
148
     */
149
    public function setDuration(int|null $value): void
150
    {
151
        $this->duration = $value;
152
    }
153
154
    /**
155
     * List of tables whose metadata should NOT be cached. Defaults to empty array. The table names may contain schema
156
     * prefix, if any. Do not quote the table names.
157
     *
158
     * @param array $value The table names.
159
     *
160
     * {@see setEnable()}
161
     */
162
    public function setExclude(array $value): void
163
    {
164
        $this->exclude = $value;
165
    }
166
167
    /**
168
     * Normalizes the cache key from a given key.
169
     *
170
     * If the given key is a string that does not contain characters `{}()/\@:` and no more than 64 characters,
171
     * then the key will be returned back as it is, integers will be converted to strings. Otherwise,
172
     * a normalized key is generated by serializing the given key and applying MD5 hashing.
173
     *
174
     * @see https://www.php-fig.org/psr/psr-16/#12-definitions
175
     *
176
     * @param mixed $key The key to be normalized.
177
     *
178
     * @throws InvalidArgumentException For invalid key.
179
     *
180
     * @return string The normalized cache key.
181
     */
182
    private function normalize(mixed $key): string
183
    {
184
        if (is_string($key) || is_int($key)) {
185
            $key = (string) $key;
186
            $length = mb_strlen($key, '8bit');
187
            return (strpbrk($key, '{}()/\@:') || $length < 1 || $length > 64) ? md5($key) : $key;
188
        }
189
190
        if (($key = json_encode($key)) === false) {
191
            throw new InvalidArgumentException('Invalid key. ' . json_last_error_msg());
192
        }
193
194
        return md5($key);
195
    }
196
197
    private function addToTag(string $key, string $cacheTag = null): void
198
    {
199
        if (empty($cacheTag)) {
200
            return;
201
        }
202
        /** @var string[] $data */
203
        $data = $this->psrCache->get($cacheTag, []);
204
        $data[] = $key;
205
        $this->psrCache->set($cacheTag, $data);
206
    }
207
}
208