Passed
Push — master ( f0915f...7925ec )
by Alexander
01:20
created

TagDependency::isChanged()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php declare(strict_types=1);
2
3
namespace Yiisoft\Cache\Dependency;
4
5
use Psr\SimpleCache\InvalidArgumentException;
6
use Yiisoft\Cache\CacheInterface;
7
8
/**
9
 * TagDependency associates a cached value with one or multiple {@see TagDependency::$tags}.
10
 *
11
 * By calling {@see TagDependency::invalidate()}, you can invalidate all cached values that are associated with the specified tag name(s).
12
 *
13
 * ```php
14
 * // setting multiple cache keys to store data forever and tagging them with "user-123"
15
 * $cache->set('user_42_profile', '', 0, new TagDependency('user-123'));
16
 * $cache->set('user_42_stats', '', 0, new TagDependency('user-123'));
17
 *
18
 * // invalidating all keys tagged with "user-123"
19
 * TagDependency::invalidate($cache, 'user-123');
20
 * ```
21
 */
22
final class TagDependency extends Dependency
23
{
24
    /**
25
     * @var array a list of tag names for this dependency
26
     */
27
    private $tags;
28
29
    /**
30
     * @param string|array $tags a list of tag names for this dependency. For a single tag, you may specify it as a string
31
     */
32 6
    public function __construct($tags)
33
    {
34 6
        $this->tags = (array)$tags;
35
    }
36
37
    /**
38
     * Generates the data needed to determine if dependency has been changed.
39
     * @param CacheInterface $cache the cache component that is currently evaluating this dependency
40
     * @return mixed the data needed to determine if dependency has been changed.
41
     * @throws InvalidArgumentException
42
     */
43 6
    protected function generateDependencyData(CacheInterface $cache): array
44
    {
45 6
        $timestamps = $this->getStoredTagTimestamps($cache, $this->tags);
46 6
        $timestamps = $this->storeTimestampsForNewTags($cache, $timestamps);
47
48 6
        return $timestamps;
49
    }
50
51 5
    public function isChanged(CacheInterface $cache): bool
52
    {
53 5
        $timestamps = $this->getStoredTagTimestamps($cache, $this->tags);
54 5
        return $timestamps !== $this->data;
55
    }
56
57
    /**
58
     * Invalidates all of the cached values that are associated with any of the specified {@see tags}.
59
     * @param CacheInterface $cache the cache component that caches the values
60
     * @param string|array $tags
61
     */
62 5
    public static function invalidate(CacheInterface $cache, $tags): void
63
    {
64 5
        $keys = self::buildCacheKeys($tags);
65 5
        self::touchKeys($cache, $keys);
66
    }
67
68
    /**
69
     * Generates the timestamp for the specified cache keys.
70
     * @param CacheInterface $cache
71
     * @param string[] $keys
72
     * @return array the timestamp indexed by cache keys
73
     */
74 5
    private static function touchKeys(CacheInterface $cache, array $keys): array
75
    {
76 5
        $values = [];
77 5
        $time = microtime();
78 5
        foreach ($keys as $key) {
79 5
            $values[$key] = $time;
80
        }
81 5
        $cache->setMultiple($values);
82 5
        return $values;
83
    }
84
85
    /**
86
     * Returns the timestamps for the specified tags.
87
     * @param CacheInterface $cache
88
     * @param string[] $tags
89
     * @return array the timestamps indexed by the specified tags.
90
     * @throws InvalidArgumentException
91
     */
92 6
    private function getStoredTagTimestamps(CacheInterface $cache, array $tags): iterable
93
    {
94 6
        if (empty($tags)) {
95 1
            return [];
96
        }
97
98 5
        $keys = self::buildCacheKeys($tags);
99
100 5
        return $cache->getMultiple($keys);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $cache->getMultiple($keys) returns the type iterable which is incompatible with the documented return type array.
Loading history...
101
    }
102
103
    /**
104
     * Builds a normalized cache key from a given tag, making sure it is short enough and safe
105
     * for any particular cache storage.
106
     * @param string $tag tag name.
107
     * @return string cache key.
108
     */
109 5
    private static function buildCacheKey(string $tag): string
110
    {
111 5
        return md5(json_encode([__CLASS__, $tag]));
112
    }
113
114
    /**
115
     * Builds array of keys from a given tags
116
     * @param mixed $tags
117
     * @return array
118
     */
119 5
    private static function buildCacheKeys($tags): array
120
    {
121 5
        $keys = [];
122 5
        foreach ((array)$tags as $tag) {
123 5
            $keys[] = self::buildCacheKey($tag);
124
        }
125
126 5
        return $keys;
127
    }
128
129
    /**
130
     * Generates and stores timestamps for tags that are not stored in the cache yet.
131
     * @param CacheInterface $cache
132
     * @param iterable $timestamps
133
     * @return array
134
     */
135 6
    private function storeTimestampsForNewTags(CacheInterface $cache, iterable $timestamps)
136
    {
137 6
        $newKeys = [];
138 6
        foreach ($timestamps as $key => $timestamp) {
139 5
            if ($timestamp === null) {
140 5
                $newKeys[] = $key;
141
            }
142
        }
143 6
        if (!empty($newKeys)) {
144 5
            $timestamps = array_merge($this->iterableToArray($timestamps), self::touchKeys($cache, $newKeys));
145
        }
146
147 6
        return $timestamps;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $timestamps also could return the type iterable which is incompatible with the documented return type array.
Loading history...
148
    }
149
}
150