Completed
Pull Request — master (#46)
by Klaus
09:50
created

CacheService::recursiveGetMulti()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 8.8977
c 0
b 0
f 0
cc 6
nc 7
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Linio\Component\Cache;
6
7
use Doctrine\Common\Inflector\Inflector;
8
use Linio\Component\Cache\Adapter\AdapterInterface;
9
use Linio\Component\Cache\Encoder\EncoderInterface;
10
use Linio\Component\Cache\Exception\InvalidConfigurationException;
11
use Linio\Component\Cache\Exception\KeyNotFoundException;
12
use Psr\Log\LoggerInterface;
13
14
class CacheService
15
{
16
    protected array $adapterStack = [];
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_ARRAY, expecting T_FUNCTION or T_CONST
Loading history...
17
    protected EncoderInterface $encoder;
18
    protected string $namespace = '';
19
    protected LoggerInterface $logger;
20
    protected array $cacheConfig = [];
21
22
    public function __construct(array $cacheConfig)
23
    {
24
        $this->validateServiceConfiguration($cacheConfig);
25
26
        $this->cacheConfig = $cacheConfig;
27
28
        // service config
29
        if (isset($cacheConfig['namespace'])) {
30
            $this->namespace = $cacheConfig['namespace'];
31
        }
32
33
        if (!isset($cacheConfig['encoder'])) {
34
            $cacheConfig['encoder'] = 'json';
35
        }
36
37
        $this->createEncoder($cacheConfig['encoder']);
38
    }
39
40
    public function getLogger(): LoggerInterface
41
    {
42
        return $this->logger;
43
    }
44
45
    public function setLogger(LoggerInterface $logger): void
46
    {
47
        $this->logger = $logger;
48
    }
49
50
    public function getNamespace(): string
51
    {
52
        return $this->namespace;
53
    }
54
55
    public function setNamespace(string $namespace): void
56
    {
57
        $this->namespace = $namespace;
58
    }
59
60
    /**
61
     * @return AdapterInterface[]
62
     */
63
    public function getAdapterStack(): array
64
    {
65
        if (empty($this->adapterStack)) {
66
            $this->createAdapterStack($this->cacheConfig);
67
        }
68
69
        return $this->adapterStack;
70
    }
71
72
    /**
73
     * @return mixed
74
     */
75
    public function get(string $key)
76
    {
77
        [$value, $success] = $this->recursiveGet($key);
78
79
        if (!$success) {
80
            return;
81
        }
82
83
        return $this->encoder->decode($value);
84
    }
85
86
    /**
87
     * @return array(mixed, bool)
88
     */
89
    protected function recursiveGet(string $key, int $level = 0): array
90
    {
91
        $adapterStack = $this->getAdapterStack();
92
93
        $adapter = $adapterStack[$level];
94
        $keyFound = true;
95
        try {
96
            $value = $adapter->get($key);
97
98
            return [$value, $keyFound];
99
        } catch (KeyNotFoundException $e) {
100
            $value = null;
101
            $keyFound = false;
102
        }
103
104
        if ($level == (count($adapterStack) - 1)) {
105
            return [$value, $keyFound];
106
        }
107
108
        [$value, $keyFound] = $this->recursiveGet($key, $level + 1);
109
110
        if ($keyFound || (!$keyFound && $adapter->cacheNotFoundKeys())) {
111
            $adapter->set($key, $value);
112
        }
113
114
        return [$value, $keyFound];
115
    }
116
117
    public function getMulti(array $keys): array
118
    {
119
        $values = $this->recursiveGetMulti($keys);
120
121
        foreach ($values as $key => $value) {
122
            $values[$key] = $this->encoder->decode($value);
123
        }
124
125
        return $values;
126
    }
127
128
    protected function recursiveGetMulti(array $keys, int $level = 0): array
129
    {
130
        $adapterStack = $this->getAdapterStack();
131
132
        $adapter = $adapterStack[$level];
133
        $values = $adapter->getMulti($keys);
134
135
        if (count($values) == count($keys) || ($level == (count($adapterStack) - 1))) {
136
            return $values;
137
        }
138
139
        $notFoundKeys = [];
140
        foreach ($keys as $key) {
141
            if (!isset($values[$key])) {
142
                $notFoundKeys[] = $key;
143
            }
144
        }
145
146
        $notFoundValues = $this->recursiveGetMulti($notFoundKeys, $level + 1);
147
        if (!empty($notFoundValues)) {
148
            $adapter->setMulti($notFoundValues);
149
        }
150
151
        return array_merge($values, $notFoundValues);
152
    }
153
154
    /**
155
     * @param mixed $value
156
     */
157
    public function set(string $key, $value): bool
158
    {
159
        $value = $this->encoder->encode($value);
160
161
        return $this->recursiveSet($key, $value);
162
    }
163
164
    /**
165
     * @param mixed $value
166
     */
167
    protected function recursiveSet(string $key, $value, int $level = null): bool
168
    {
169
        $adapterStack = $this->getAdapterStack();
170
171
        if ($level === null) {
172
            $level = count($adapterStack) - 1;
173
        }
174
175
        $adapter = $adapterStack[$level];
176
        $result = $adapter->set($key, $value);
177
178
        if ($level == 0) {
179
            return true;
180
        }
181
182
        if (($result === false) && ($level == count($adapterStack) - 1)) {
183
            return false;
184
        }
185
186
        return $this->recursiveSet($key, $value, $level - 1);
187
    }
188
189
    public function setMulti(array $data): bool
190
    {
191
        foreach ($data as $key => $value) {
192
            $data[$key] = $this->encoder->encode($value);
193
        }
194
195
        return $this->recursiveSetMulti($data);
196
    }
197
198
    protected function recursiveSetMulti(array $data, int $level = null): bool
199
    {
200
        $adapterStack = $this->getAdapterStack();
201
202
        if ($level === null) {
203
            $level = count($adapterStack) - 1;
204
        }
205
206
        $adapter = $adapterStack[$level];
207
        $result = $adapter->setMulti($data);
208
209
        if ($level == 0) {
210
            return true;
211
        }
212
213
        if (($result === false) && ($level == count($adapterStack) - 1)) {
214
            return false;
215
        }
216
217
        return $this->recursiveSetMulti($data, $level - 1);
218
    }
219
220
    public function contains(string $key): bool
221
    {
222
        return $this->recursiveContains($key);
223
    }
224
225
    protected function recursiveContains(string $key, int $level = 0): bool
226
    {
227
        $adapterStack = $this->getAdapterStack();
228
229
        $adapter = $adapterStack[$level];
230
        $value = $adapter->contains($key);
231
        if (($value !== false) || ($level == (count($adapterStack) - 1))) {
232
            return $value;
233
        }
234
235
        return $this->recursiveContains($key, $level + 1);
236
    }
237
238
    public function delete(string $key): bool
239
    {
240
        $adapterStack = $this->getAdapterStack();
241
242
        foreach ($adapterStack as $adapter) {
243
            $adapter->delete($key);
244
        }
245
246
        return true;
247
    }
248
249
    public function deleteMulti(array $keys): bool
250
    {
251
        $adapterStack = $this->getAdapterStack();
252
253
        foreach ($adapterStack as $adapter) {
254
            $adapter->deleteMulti($keys);
255
        }
256
257
        return true;
258
    }
259
260
    public function flush(): bool
261
    {
262
        $adapterStack = $this->getAdapterStack();
263
264
        foreach ($adapterStack as $adapter) {
265
            $adapter->flush();
266
        }
267
268
        return true;
269
    }
270
271
    /**
272
     * @throws InvalidConfigurationException
273
     */
274
    protected function createAdapterStack(array $cacheConfig): void
275
    {
276
        foreach ($cacheConfig['layers'] as $adapterConfig) {
277
            $this->validateAdapterConfig($adapterConfig);
278
279
            $adapterClass = sprintf('%s\\Adapter\\%sAdapter', __NAMESPACE__, Inflector::classify($adapterConfig['adapter_name']));
280
281
            if (!class_exists($adapterClass)) {
282
                throw new InvalidConfigurationException('Adapter class does not exist: ' . $adapterClass);
283
            }
284
285
            /** @var AdapterInterface $adapterInstance */
286
            $adapterInstance = new $adapterClass($adapterConfig['adapter_options']);
287
            $adapterInstance->setNamespace($this->namespace);
288
289
            $this->adapterStack[] = $adapterInstance;
290
        }
291
    }
292
293
    /**
294
     * @throws InvalidConfigurationException
295
     */
296
    protected function createEncoder(string $encoderName): void
297
    {
298
        $encoderClass = sprintf('%s\\Encoder\\%sEncoder', __NAMESPACE__, Inflector::classify($encoderName));
299
300
        if (!class_exists($encoderClass)) {
301
            throw new InvalidConfigurationException('Encoder class does not exist: ' . $encoderClass);
302
        }
303
304
        $this->encoder = new $encoderClass();
305
    }
306
307
    /**
308
     * @throws InvalidConfigurationException
309
     */
310
    protected function validateAdapterConfig(array $adapterConfig): void
311
    {
312
        if (!isset($adapterConfig['adapter_name'])) {
313
            throw new InvalidConfigurationException('Missing required configuration option: adapter_name');
314
        }
315
316
        if (!isset($adapterConfig['adapter_options'])) {
317
            throw new InvalidConfigurationException('Missing required configuration option: adapter_options');
318
        }
319
    }
320
321
    /**
322
     * @throws InvalidConfigurationException
323
     */
324
    protected function validateServiceConfiguration(array $cacheConfig): void
325
    {
326
        if (!isset($cacheConfig['layers'])) {
327
            throw new InvalidConfigurationException('Missing required cache configuration parameter: layers');
328
        }
329
    }
330
}
331