TagDependency::buildCacheKey()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 9
ccs 5
cts 5
cp 1
rs 10
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Cache\Dependency;
6
7
use Yiisoft\Cache\CacheInterface;
8
use Yiisoft\Cache\Exception\InvalidArgumentException;
9
10
use function json_encode;
11
use function json_last_error_msg;
12
use function md5;
13
use function microtime;
14
15
/**
16
 * TagDependency associates a cached value with one or multiple {@see TagDependency::$tags}.
17
 *
18
 * By calling {@see TagDependency::invalidate()}, you can invalidate all
19
 * cached values that are associated with the specified tag name(s).
20
 *
21
 * ```php
22
 * // setting multiple cache keys to store data forever and tagging them with "user-123"
23
 * $cache->getOrSet('user_42_profile', '', null, new TagDependency('user-123'));
24
 * $cache->getOrSet('user_42_stats', '', null, new TagDependency('user-123'));
25
 *
26
 *  // setting a cache key to store data and tagging them with "user-123" with the specified TTL for the tag
27
 * $cache->getOrSet('user_42_profile', '', null, new TagDependency('user-123', 3600));
28
 *
29
 * // invalidating all keys tagged with "user-123"
30
 * TagDependency::invalidate($cache, 'user-123');
31
 * ```
32
 */
33
final class TagDependency extends Dependency
34
{
35
    /**
36
     * @var array List of tag names for this dependency.
37
     */
38
    private array $tags;
39
40
    /**
41
     * @var int|null The TTL value of this item. null means infinity.
42
     */
43
    private ?int $ttl;
44
45
    /**
46
     * @param array|string $tags List of tag names for this dependency.
47
     * For a single tag, you may specify it as a string.
48
     * @param int|null $ttl The TTL value of this item. null means infinity.
49
     */
50 25
    public function __construct(array|string $tags, int $ttl = null)
51
    {
52 25
        $this->tags = (array) $tags;
53
54 25
        if ($ttl !== null && $ttl < 1) {
55 1
            throw new InvalidArgumentException(
56
                'TTL must be a positive number or null, to invalidate tags, use the'
57
                . ' static `\Yiisoft\Cache\Dependency\TagDependency::invalidate()` method.',
58
            );
59
        }
60
61 24
        $this->ttl = $ttl;
62
    }
63
64 24
    protected function generateDependencyData(CacheInterface $cache): array
65
    {
66 24
        if (empty($this->tags)) {
67 1
            return [];
68
        }
69
70 23
        $tags = [];
71
72 23
        foreach ($this->getTagsData($cache) as $tag => $time) {
73 22
            $tags[$tag] = $time ?? microtime();
74
        }
75
76
        $cache
77 22
            ->psr()
78 22
            ->setMultiple($tags, $this->ttl);
79
80 22
        return $tags;
81
    }
82
83 16
    public function isChanged(CacheInterface $cache): bool
84
    {
85 16
        if (empty($this->tags)) {
86 1
            return $this->data !== [];
87
        }
88
89 15
        return $this->data !== $this->getTagsData($cache);
90
    }
91
92
    /**
93
     * Invalidates all of the cached values that are associated with any of the specified {@see tags}.
94
     *
95
     * @param CacheInterface $cache The cache component that caches the values.
96
     * @param array|string $tags List of tag names.
97
     */
98 8
    public static function invalidate(CacheInterface $cache, array|string $tags): void
99
    {
100
        $cache
101 8
            ->psr()
102 8
            ->deleteMultiple(self::buildCacheKeys((array) $tags));
103
    }
104
105
    /**
106
     * Builds a normalized cache key from a given tag, making sure it is short enough and safe
107
     * for any particular cache storage.
108
     *
109
     * @param string $tag The tag name.
110
     *
111
     * @return string The cache key.
112
     */
113 23
    private static function buildCacheKey(string $tag): string
114
    {
115 23
        $jsonTag = json_encode([self::class, $tag]);
116
117 23
        if ($jsonTag === false) {
118 1
            throw new InvalidArgumentException('Invalid tag. ' . json_last_error_msg() . '.');
119
        }
120
121 22
        return md5($jsonTag);
122
    }
123
124
    /**
125
     * Builds array of keys from a given tags.
126
     *
127
     * @return string[]
128
     */
129 23
    private static function buildCacheKeys(array $tags): array
130
    {
131 23
        $keys = [];
132
133
        foreach ($tags as $tag) {
134 23
            $keys[] = self::buildCacheKey((string) $tag);
135 23
        }
136
137
        return $keys;
138 22
    }
139
140
    /**
141
     * Gets the tags data from the cache storage.
142
     *
143
     * @psalm-return array<array-key, string|null>
144
     */
145
    private function getTagsData(CacheInterface $cache): array
146
    {
147
        /** @psalm-var array<array-key, string|null> */
148
        return $this->iterableToArray($cache
149 23
                ->psr()
150
                ->getMultiple(self::buildCacheKeys($this->tags)));
151
    }
152
}
153