Completed
Push — inmemory-init ( b4ebef )
by André
21:10
created

AbstractInMemoryHandler::getMultipleCacheValues()   C

Complexity

Conditions 10
Paths 40

Size

Total Lines 78

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
nc 40
nop 6
dl 0
loc 78
rs 6.6133
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * File containing the ContentHandler implementation.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\Persistence\Cache;
10
11
use eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache;
12
use eZ\Publish\SPI\Persistence\Handler as PersistenceHandler;
13
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
14
15
/**
16
 * Class AbstractInMemoryHandler.
17
 *
18
 * Abstract handler for use in other Persistence Cache Handlers.
19
 */
20
abstract class AbstractInMemoryHandler
21
{
22
    /**
23
     * NOTE: $cache and $inMemory is private in order to abstract interactions across both,
24
     *       and be able to optimize this later without affecting all classes extending this.
25
     *
26
     * @var \Symfony\Component\Cache\Adapter\TagAwareAdapterInterface
27
     */
28
    private $cache;
29
30
    /**
31
     * @var \eZ\Publish\SPI\Persistence\Handler
32
     */
33
    protected $persistenceHandler;
34
35
    /**
36
     * @var \eZ\Publish\Core\Persistence\Cache\PersistenceLogger
37
     */
38
    protected $logger;
39
40
    /**
41
     * @var \eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache
42
     */
43
    private $inMemory;
44
45
    /**
46
     * Setups current handler with everything needed.
47
     *
48
     * @param \Symfony\Component\Cache\Adapter\TagAwareAdapterInterface $cache
49
     * @param \eZ\Publish\SPI\Persistence\Handler $persistenceHandler
50
     * @param \eZ\Publish\Core\Persistence\Cache\PersistenceLogger $logger
51
     * @param \eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache $inMemory
52
     */
53
    public function __construct(
54
        TagAwareAdapterInterface $cache,
55
        PersistenceHandler $persistenceHandler,
56
        PersistenceLogger $logger,
57
        InMemoryCache $inMemory
58
    ) {
59
        $this->cache = $cache;
60
        $this->persistenceHandler = $persistenceHandler;
61
        $this->logger = $logger;
62
        $this->inMemory = $inMemory;
63
64
        $this->init();
65
    }
66
67
    /**
68
     * Optional dunction to initialize handler without having to overload __construct().
69
     */
70
    protected function init(): void
71
    {
72
        // overload to add init logic if needed in handler
73
    }
74
75
    /**
76
     * Load one cache item from cache and loggs the hits / misses.
77
     *
78
     * Load items from in-memory cache, symfony cache pool or backend in that order.
79
     * If not cached the returned objects will be placed in cache.
80
     *
81
     * @param int|string $id
82
     * @param string $keyPrefix E.g "ez-content-"
83
     * @param callable $backendLoader Function for loading missing objects, gets array with missing id's as argument,
84
     *                                expects return value to be array with id as key. Missing items should be missing.
85
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
86
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
87
     * @param string $keySuffix Optional, e.g "-by-identifier"
88
     *
89
     * @return object
90
     */
91
    final protected function getCacheValue(
92
        $id,
93
        string $keyPrefix,
94
        callable $backendLoader,
95
        callable $cacheTagger,
96
        callable $cacheIndexes,
97
        string $keySuffix = ''
98
    ) {
99
        $key = $keyPrefix . $id . $keySuffix;
100
        // In-memory
101
        if ($object = $this->inMemory->get($key)) {
102
            $this->logger->logCacheHit([$id], 3, true);
103
104
            return $object;
105
        }
106
107
        // Cache pool
108
        $cacheItem = $this->cache->getItem($key);
109 View Code Duplication
        if ($cacheItem->isHit()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
110
            $this->logger->logCacheHit([$id], 3);
111
            $this->inMemory->setMulti([$object = $cacheItem->get()], $cacheIndexes);
112
113
            return $object;
114
        }
115
116
        // Backend
117
        $object = $backendLoader($id);
118
        $this->inMemory->setMulti([$object], $cacheIndexes);
119
        $this->logger->logCacheMiss([$id], 3);
120
        $this->cache->save(
121
            $cacheItem
122
                ->set($object)
123
                ->tag($cacheTagger($object))
124
        );
125
126
        return $object;
127
    }
128
129
    /**
130
     * Load list of objects of some type and loggs the hits / misses.
131
     *
132
     * Load items from in-memory cache, symfony cache pool or backend in that order.
133
     * If not cached the returned objects will be placed in cache.
134
     *
135
     * @param string $key
136
     * @param callable $backendLoader Function for loading ALL objects, value is cached as-is.
137
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
138
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
139
     * @param callable $listTags Optional, global tags for the list cache.
140
     * @param array $arguments Optional, arguments when parnt method takes arguments that key varies on.
141
     *
142
     * @return object
143
     */
144
    final protected function getListCacheValue(
145
        string $key,
146
        callable $backendLoader,
147
        callable $cacheTagger,
148
        callable $cacheIndexes,
149
        callable $listTags = null,
150
        array $arguments = []
151
    ) {
152
        // In-memory
153
        if ($objects = $this->inMemory->get($key)) {
154
            $this->logger->logCacheHit($arguments, 3, true);
155
156
            return $objects;
157
        }
158
159
        // Cache pool
160
        $cacheItem = $this->cache->getItem($key);
161 View Code Duplication
        if ($cacheItem->isHit()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
162
            $this->logger->logCacheHit($arguments, 3);
163
            $this->inMemory->setMulti($objects = $cacheItem->get(), $cacheIndexes, $key);
164
165
            return $objects;
166
        }
167
168
        // Backend
169
        $objects = $backendLoader();
170
        $this->inMemory->setMulti($objects, $cacheIndexes, $key);
171
        $this->logger->logCacheMiss($arguments, 3);
172
173
        if ($listTags !== null) {
174
            $tagSet = [$listTags()];
175
        } else {
176
            $tagSet = [[]];
177
        }
178
179
        foreach ($objects as $object) {
180
            $tagSet[] = $cacheTagger($object);
181
        }
182
183
        $this->cache->save(
184
            $cacheItem
185
                ->set($objects)
186
                ->tag(array_unique(array_merge(...$tagSet)))
187
        );
188
189
        return $objects;
190
    }
191
192
    /**
193
     * Load several cache items from cache and loggs the hits / misses.
194
     *
195
     * Load items from in-memory cache, symfony cache pool or backend in that order.
196
     * If not cached the returned objects will be placed in cache.
197
     *
198
     * Cache items must be stored with a key in the following format "${keyPrefix}${id}", like "ez-content-info-${id}",
199
     * in order for this method to be able to prefix key on id's and also extract key prefix afterwards.
200
     *
201
     * @param array $ids
202
     * @param string $keyPrefix E.g "ez-content-"
203
     * @param callable $backendLoader Function for loading missing objects, gets array with missing id's as argument,
204
     *                                expects return value to be array with id as key. Missing items should be missing.
205
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
206
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
207
     * @param string $keySuffix Optional, e.g "-by-identifier"
208
     *
209
     * @return array
210
     */
211
    final protected function getMultipleCacheValues(
212
        array $ids,
213
        string $keyPrefix,
214
        callable $backendLoader,
215
        callable $cacheTagger,
216
        callable $cacheIndexes,
217
        string $keySuffix = ''
218
    ): array {
219
        if (empty($ids)) {
220
            return [];
221
        }
222
223
        // Generate unique cache keys and check if in-memory
224
        $list = [];
225
        $cacheKeys = [];
226
        $cacheKeysToIdMap = [];
227
        foreach (array_unique($ids) as $id) {
228
            $key = $keyPrefix . $id . $keySuffix;
229
            if ($object = $this->inMemory->get($key)) {
230
                $list[$id] = $object;
231
            } else {
232
                $cacheKeys[] = $key;
233
                $cacheKeysToIdMap[$key] = $id;
234
            }
235
        }
236
237
        // No in-memory misses
238
        if (empty($cacheKeys)) {
239
            $this->logger->logCacheHit($ids, 3, true);
240
241
            return $list;
242
        }
243
244
        // Load cache items by cache keys (will contain hits and misses)
245
        $loaded = [];
246
        $cacheMisses = [];
247
        foreach ($this->cache->getItems($cacheKeys) as $key => $cacheItem) {
248
            $id = $cacheKeysToIdMap[$key];
249
            if ($cacheItem->isHit()) {
250
                $list[$id] = $cacheItem->get();
251
                $loaded[$id] = $list[$id];
252
            } else {
253
                $cacheMisses[] = $id;
254
                $list[$id] = $cacheItem;
255
            }
256
        }
257
258
        // No cache pool misses, cache loaded items in-memory and return
259
        if (empty($cacheMisses)) {
260
            $this->logger->logCacheHit($ids, 3, true);
261
            $this->inMemory->setMulti($loaded, $cacheIndexes);
262
263
            return $list;
264
        }
265
266
        // Load missing items, save to cache & apply to list if found
267
        $backendLoadedList = $backendLoader($cacheMisses);
268
        foreach ($cacheMisses as $id) {
269
            if (isset($backendLoadedList[$id])) {
270
                $this->cache->save(
271
                    $list[$id]
272
                        ->set($backendLoadedList[$id])
273
                        ->tag($cacheTagger($backendLoadedList[$id]))
274
                );
275
                $loaded[$id] = $backendLoadedList[$id];
276
                $list[$id] = $backendLoadedList[$id];
277
            } else {
278
                // not found
279
                unset($list[$id]);
280
            }
281
        }
282
283
        $this->inMemory->setMulti($loaded, $cacheIndexes);
284
        unset($loaded, $backendLoadedList);
285
        $this->logger->logCacheMiss($cacheMisses, 3);
286
287
        return $list;
288
    }
289
290
    /**
291
     * Delete cache by keys.
292
     *
293
     * @param array $keys
294
     */
295
    final protected function deleteCache(array $keys): void
296
    {
297
        if (empty($keys)) {
298
            return;
299
        }
300
301
        $this->inMemory->deleteMulti($keys);
302
        $this->cache->deleteItems($keys);
303
    }
304
305
    /**
306
     * Invalidate cache by tags.
307
     *
308
     * @param array $tags
309
     */
310
    final protected function invalidateCache(array $tags): void
311
    {
312
        if (empty($tags)) {
313
            return;
314
        }
315
316
        $this->inMemory->clear();
317
        $this->cache->invalidateTags($tags);
318
    }
319
320
    /**
321
     * Clear ALL cache.
322
     *
323
     * Only use this in extname situations, or for isolation in tests.
324
     */
325
    final protected function clearCache(): void
326
    {
327
        $this->inMemory->clear();
328
        $this->cache->clear();
329
    }
330
}
331