Issues (6)

src/TieredCache.php (1 issue)

Labels
Severity
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 2
     * fastest caches first.
49
     */
50 2
    public function __construct(Cache|array|null $caches = null)
51 2
    {
52
        if (!is_array($caches)) {
53 2
            $caches = func_get_args();
54 2
        }
55 1
        foreach ($caches as $i => $cache) {
56
            if (!($cache instanceof Cache)) {
57 1
                throw new InvalidArgumentException("Argument {$i} is not of class Cache");
58
            }
59 1
            $this->caches[] = $cache;
60
        }
61
    }
62
63
    /**
64 1
     * @inheritDoc
65
     */
66 1
    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
            if ($value !== null) {
72
                return $value;
73 1
            }
74
        }
75
        return $default;
76
    }
77
78
    /**
79 1
     * @inheritDoc
80
     */
81 1
    public function set(string $key, mixed $value, DateInterval|int|null $ttl = null) : bool
82
    {
83
        return $this->any('set', $this->key($key), $value, $this->ttl($ttl));
84
    }
85
86
    /**
87 1
     * @inheritDoc
88
     */
89 1
    public function delete(string $key) : bool
90
    {
91
        return $this->all('delete', $this->key($key));
92
    }
93
94
    /**
95 1
     * @inheritDoc
96
     */
97 1
    public function clean() : bool
98
    {
99
        return $this->all('clean');
100
    }
101
102
    /**
103 1
     * @inheritDoc
104
     */
105 1
    public function flush() : bool
106
    {
107
        return $this->all('flush');
108
    }
109
110
    /**
111 1
     * @inheritDoc
112
     */
113 1
    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
                $values,
121 1
                array_filter(is_array($result) ? $result : iterator_to_array($result, true))
122 1
            );
123
            if (count($values) === count($keys)) {
124
                return $values;
125 1
            }
126
127
            $neededKeys = array_diff($keys, $values);
0 ignored issues
show
$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 1
130 1
        /* Finally, set defaults */
131 1
        foreach ($keys as $key) {
132
            if (!isset($values[$key])) {
133
                $values[$key] = $default;
134
            }
135 1
        }
136
137
        return $values;
138
    }
139
140
    /**
141 1
     * @inheritDoc
142
     */
143 1
    public function setMultiple(iterable $values, DateInterval|int|null $ttl = null) : bool
144
    {
145
        return $this->any('setMultiple', $this->values($values), $this->ttl($ttl));
146
    }
147
148
    /**
149 1
     * @inheritDoc
150
     */
151 1
    public function deleteMultiple(iterable $keys) : bool
152
    {
153
        return $this->all('deleteMultiple', $this->keys($keys));
154
    }
155
156
    /**
157 1
     * @inheritDoc
158
     */
159 1
    public function clear() : bool
160
    {
161
        return $this->flush();
162
    }
163
164
    /**
165 1
     * @inheritDoc
166
     */
167 1
    public function has(string $key) : bool
168
    {
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 1
     * @return bool True if the operation was successful on all caches
178
     */
179 1
    private function all(string $call, ...$args) : bool
180 1
    {
181 1
        $success = true;
182
        foreach ($this->caches as $cache) {
183 1
            $result = ([$cache, $call])(...$args);
184
            $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 1
     * @param mixed ...$args The method's arguments
194
     * @return bool True if the operation was successful on any cache
195 1
     */
196 1
    private function any(string $call, ...$args) : bool
197 1
    {
198
        $success = false;
199 1
        foreach ($this->caches as $cache) {
200
            $result = ([$cache, $call])(...$args);
201
            $success = $success || $result;
202
        }
203
        return $success;
204
    }
205
}
206