Completed
Pull Request — master (#32)
by Alexander
01:45
created

TagDependency::getTimestamps()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 12
ccs 6
cts 7
cp 0.8571
rs 10
cc 3
nc 3
nop 2
crap 3.0261
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 4
    public function __construct($tags)
33
    {
34 4
        $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 4
    protected function generateDependencyData(CacheInterface $cache): array
44
    {
45 4
        $timestamps = $this->getStoredTagTimestamps($cache, $this->tags);
46
47 4
        $newKeys = [];
48 4
        foreach ($timestamps as $key => $timestamp) {
49 4
            if ($timestamp === null) {
50 4
                $newKeys[] = $key;
51
            }
52
        }
53 4
        if (!empty($newKeys)) {
54 4
            $timestamps = array_merge($timestamps, self::touchKeys($cache, $newKeys));
55
        }
56
57 4
        return $timestamps;
58
    }
59
60 4
    public function isChanged(CacheInterface $cache): bool
61
    {
62 4
        $timestamps = $this->getStoredTagTimestamps($cache, $this->tags);
63 4
        return $timestamps !== $this->data;
64
    }
65
66
    /**
67
     * Invalidates all of the cached values that are associated with any of the specified {@see tags}.
68
     * @param CacheInterface $cache the cache component that caches the values
69
     * @param string|array $tags
70
     */
71 4
    public static function invalidate(CacheInterface $cache, $tags): void
72
    {
73 4
        $keys = [];
74 4
        foreach ((array)$tags as $tag) {
75 4
            $keys[] = self::buildCacheKey($tag);
76
        }
77 4
        self::touchKeys($cache, $keys);
78
    }
79
80
    /**
81
     * Generates the timestamp for the specified cache keys.
82
     * @param CacheInterface $cache
83
     * @param string[] $keys
84
     * @return array the timestamp indexed by cache keys
85
     */
86 4
    private static function touchKeys(CacheInterface $cache, array $keys): array
87
    {
88 4
        $values = [];
89 4
        $time = microtime();
90 4
        foreach ($keys as $key) {
91 4
            $values[$key] = $time;
92
        }
93 4
        $cache->setMultiple($values);
94 4
        return $values;
95
    }
96
97
    /**
98
     * Returns the timestamps for the specified tags.
99
     * @param CacheInterface $cache
100
     * @param string[] $tags
101
     * @return array the timestamps indexed by the specified tags.
102
     * @throws InvalidArgumentException
103
     */
104 4
    private function getStoredTagTimestamps(CacheInterface $cache, array $tags): iterable
105
    {
106 4
        if (empty($tags)) {
107
            return [];
108
        }
109
110 4
        $keys = [];
111 4
        foreach ($tags as $tag) {
112 4
            $keys[] = self::buildCacheKey($tag);
113
        }
114
115 4
        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...
116
    }
117
118
    /**
119
     * Builds a normalized cache key from a given tag, making sure it is short enough and safe
120
     * for any particular cache storage.
121
     * @param string $tag tag name.
122
     * @return string cache key.
123
     */
124 4
    private static function buildCacheKey(string $tag): string
125
    {
126 4
        return md5(json_encode([__CLASS__, $tag]));
127
    }
128
}
129