TieredCache::getMultiple()   A
last analyzed

Complexity

Conditions 6
Paths 7

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 14
c 1
b 0
f 0
dl 0
loc 25
ccs 14
cts 14
cp 1
rs 9.2222
cc 6
nc 7
nop 2
crap 6
1
<?php
2
3
namespace Vectorface\Cache;
4
5
use DateInterval;
6
use InvalidArgumentException;
7
use Vectorface\Cache\Common\PSR16Util;
8
9
/**
10
 * This cache's speed is dependent on underlying caches, usually medium according to basic benchmarks:
11
 *
12
 * Parameters:
13
 *   MCCache + SQLCache
14
 *   9-byte key
15
 *   151-byte value
16
 *   10000-iteration test
17
 *
18
 * Result:
19
 *   11.9338240623 seconds
20
 *
21
 * Conclusion:
22
 *   Capable of approximately 837.96 requests/second
23
 */
24
25
/**
26
 * A cache composed of other caches layered on top of one another.
27
 */
28
class TieredCache implements Cache
29
{
30
    use PSR16Util;
31
32
    /**
33
     * The cache layers.
34
     *
35
     * @var Cache[]
36
     */
37
    private array $caches = [];
38
39
    /**
40
     * Create a cache that layers caches on top of each other.
41
     *
42
     * Read requests hit caches in order until they get a hit. The first hit is returned.
43
     * Write operations hit caches in order, performing the write operation on all caches.
44
     *
45
     * @param Cache|Cache[]|null $caches An array of objects implementing the Cache interface.
46
     *
47
     * Note: Order is important. The first element is get/set first, and so on. Usually that means  you want to put the
48
     * fastest caches first.
49 2
     */
50
    public function __construct(Cache|array|null $caches = null)
51 2
    {
52 2
        if (!is_array($caches)) {
53
            $caches = func_get_args();
54 2
        }
55 2
        foreach ($caches as $i => $cache) {
56 1
            if (!($cache instanceof Cache)) {
57
                throw new InvalidArgumentException("Argument {$i} is not of class Cache");
58 1
            }
59
            $this->caches[] = $cache;
60 1
        }
61
    }
62
63
    /**
64
     * @inheritDoc
65 1
     */
66
    public function get(string $key, mixed $default = null) : mixed
67 1
    {
68 1
        $key = $this->key($key);
69 1
        foreach ($this->caches as $cache) {
70 1
            $value = $cache->get($key, null);
71 1
            if ($value !== null) {
72
                return $value;
73
            }
74 1
        }
75
        return $default;
76
    }
77
78
    /**
79
     * @inheritDoc
80 1
     */
81
    public function set(string $key, mixed $value, DateInterval|int|null $ttl = null) : bool
82 1
    {
83
        return $this->any('set', $this->key($key), $value, $this->ttl($ttl));
84
    }
85
86
    /**
87
     * @inheritDoc
88 1
     */
89
    public function delete(string $key) : bool
90 1
    {
91
        return $this->all('delete', $this->key($key));
92
    }
93
94
    /**
95
     * @inheritDoc
96 1
     */
97
    public function clean() : bool
98 1
    {
99
        return $this->all('clean');
100
    }
101
102
    /**
103
     * @inheritDoc
104 1
     */
105
    public function flush() : bool
106 1
    {
107
        return $this->all('flush');
108
    }
109
110
    /**
111
     * @inheritDoc
112 1
     */
113
    public function getMultiple(iterable $keys, mixed $default = null) : iterable
114 1
    {
115 1
        $neededKeys = $keys;
116 1
        $values = [];
117 1
        foreach ($this->caches as $cache) {
118 1
            $result = $cache->getMultiple($neededKeys);
119 1
            $values = array_merge(
120 1
                $values,
121
                array_filter(is_array($result) ? $result : iterator_to_array($result, true))
122 1
            );
123 1
            if (count($values) === count($keys)) {
124
                return $values;
125
            }
126 1
127
            $neededKeys = array_diff($keys, $values);
0 ignored issues
show
Bug introduced by
$keys of type iterable is incompatible with the type array expected by parameter $array of array_diff(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

127
            $neededKeys = array_diff(/** @scrutinizer ignore-type */ $keys, $values);
Loading history...
128
        }
129
130 1
        /* Finally, set defaults */
131 1
        foreach ($keys as $key) {
132 1
            if (!isset($values[$key])) {
133
                $values[$key] = $default;
134
            }
135
        }
136 1
137
        return $values;
138
    }
139
140
    /**
141
     * @inheritDoc
142 1
     */
143
    public function setMultiple(iterable $values, DateInterval|int|null $ttl = null) : bool
144 1
    {
145
        return $this->any('setMultiple', $this->values($values), $this->ttl($ttl));
146
    }
147
148
    /**
149
     * @inheritDoc
150 1
     */
151
    public function deleteMultiple(iterable $keys) : bool
152 1
    {
153
        return $this->all('deleteMultiple', $this->keys($keys));
154
    }
155
156
    /**
157
     * @inheritDoc
158 1
     */
159
    public function clear() : bool
160 1
    {
161
        return $this->flush();
162
    }
163
164
    /**
165
     * @inheritDoc
166 1
     */
167
    public function has(string $key) : bool
168 1
    {
169
        return $this->get($this->key($key)) !== null;
170
    }
171
172
    /**
173
     * Run a method on all caches, expect all caches to success for success
174
     *
175
     * @param string $call The cache interface method to be called
176
     * @param mixed ...$args The method's arguments
177
     * @return bool True if the operation was successful on all caches
178 1
     */
179
    private function all(string $call, ...$args) : bool
180 1
    {
181 1
        $success = true;
182 1
        foreach ($this->caches as $cache) {
183
            $result = ([$cache, $call])(...$args);
184 1
            $success = $success && $result;
185
        }
186
        return $success;
187
    }
188
189
    /**
190
     * Run a method on all caches, expect any successful result for success
191
     *
192
     * @param string $call The cache interface method to be called
193
     * @param mixed ...$args The method's arguments
194 1
     * @return bool True if the operation was successful on any cache
195
     */
196 1
    private function any(string $call, ...$args) : bool
197 1
    {
198 1
        $success = false;
199
        foreach ($this->caches as $cache) {
200 1
            $result = ([$cache, $call])(...$args);
201
            $success = $success || $result;
202
        }
203
        return $success;
204
    }
205
}
206