Completed
Push — ezp-30229-alternative ( 33cbfb...37c73f )
by
unknown
26:25
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
65
    /**
66
     * Load one cache item from cache and loggs the hits / misses.
67
     *
68
     * Load items from in-memory cache, symfony cache pool or backend in that order.
69
     * If not cached the returned objects will be placed in cache.
70
     *
71
     * @param int|string $id
72
     * @param string $keyPrefix E.g "ez-content-"
73
     * @param callable $backendLoader Function for loading missing objects, gets array with missing id's as argument,
74
     *                                expects return value to be array with id as key. Missing items should be missing.
75
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
76
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
77
     * @param string $keySuffix Optional, e.g "-by-identifier"
78
     *
79
     * @return object
80
     */
81
    final protected function getCacheValue(
82
        $id,
83
        string $keyPrefix,
84
        callable $backendLoader,
85
        callable $cacheTagger,
86
        callable $cacheIndexes,
87
        string $keySuffix = ''
88
    ) {
89
        $key = $keyPrefix . $id . $keySuffix;
90
        // In-memory
91
        if ($object = $this->inMemory->get($key)) {
92
            $this->logger->logCacheHit([$id], 3, true);
93
94
            return $object;
95
        }
96
97
        // Cache pool
98
        $cacheItem = $this->cache->getItem($key);
99 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...
100
            $this->logger->logCacheHit([$id], 3);
101
            $this->inMemory->setMulti([$object = $cacheItem->get()], $cacheIndexes);
102
103
            return $object;
104
        }
105
106
        // Backend
107
        $object = $backendLoader($id);
108
        $this->inMemory->setMulti([$object], $cacheIndexes);
109
        $this->logger->logCacheMiss([$id], 3);
110
        $this->cache->save(
111
            $cacheItem
112
                ->set($object)
113
                ->tag($cacheTagger($object))
114
        );
115
116
        return $object;
117
    }
118
119
    /**
120
     * Load list of objects of some type and loggs the hits / misses.
121
     *
122
     * Load items from in-memory cache, symfony cache pool or backend in that order.
123
     * If not cached the returned objects will be placed in cache.
124
     *
125
     * @param string $key
126
     * @param callable $backendLoader Function for loading ALL objects, value is cached as-is.
127
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
128
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
129
     * @param callable $listTags Optional, global tags for the list cache.
130
     * @param array $arguments Optional, arguments when parnt method takes arguments that key varies on.
131
     *
132
     * @return object
133
     */
134
    final protected function getListCacheValue(
135
        string $key,
136
        callable $backendLoader,
137
        callable $cacheTagger,
138
        callable $cacheIndexes,
139
        callable $listTags = null,
140
        array $arguments = []
141
    ) {
142
        // In-memory
143
        if ($objects = $this->inMemory->get($key)) {
144
            $this->logger->logCacheHit($arguments, 3, true);
145
146
            return $objects;
147
        }
148
149
        // Cache pool
150
        $cacheItem = $this->cache->getItem($key);
151 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...
152
            $this->logger->logCacheHit($arguments, 3);
153
            $this->inMemory->setMulti($objects = $cacheItem->get(), $cacheIndexes, $key);
154
155
            return $objects;
156
        }
157
158
        // Backend
159
        $objects = $backendLoader();
160
        $this->inMemory->setMulti($objects, $cacheIndexes, $key);
161
        $this->logger->logCacheMiss($arguments, 3);
162
163
        if ($listTags !== null) {
164
            $tagSet = [$listTags()];
165
        } else {
166
            $tagSet = [[]];
167
        }
168
169
        foreach ($objects as $object) {
170
            $tagSet[] = $cacheTagger($object);
171
        }
172
173
        $this->cache->save(
174
            $cacheItem
175
                ->set($objects)
176
                ->tag(array_unique(array_merge(...$tagSet)))
177
        );
178
179
        return $objects;
180
    }
181
182
    /**
183
     * Load several cache items from cache and loggs the hits / misses.
184
     *
185
     * Load items from in-memory cache, symfony cache pool or backend in that order.
186
     * If not cached the returned objects will be placed in cache.
187
     *
188
     * Cache items must be stored with a key in the following format "${keyPrefix}${id}", like "ez-content-info-${id}",
189
     * in order for this method to be able to prefix key on id's and also extract key prefix afterwards.
190
     *
191
     * @param array $ids
192
     * @param string $keyPrefix E.g "ez-content-"
193
     * @param callable $backendLoader Function for loading missing objects, gets array with missing id's as argument,
194
     *                                expects return value to be array with id as key. Missing items should be missing.
195
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
196
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
197
     * @param string $keySuffix Optional, e.g "-by-identifier"
198
     *
199
     * @return array
200
     */
201
    final protected function getMultipleCacheValues(
202
        array $ids,
203
        string $keyPrefix,
204
        callable $backendLoader,
205
        callable $cacheTagger,
206
        callable $cacheIndexes,
207
        string $keySuffix = ''
208
    ): array {
209
        if (empty($ids)) {
210
            return [];
211
        }
212
213
        // Generate unique cache keys and check if in-memory
214
        $list = [];
215
        $cacheKeys = [];
216
        $cacheKeysToIdMap = [];
217
        foreach (array_unique($ids) as $id) {
218
            $key = $keyPrefix . $id . $keySuffix;
219
            if ($object = $this->inMemory->get($key)) {
220
                $list[$id] = $object;
221
            } else {
222
                $cacheKeys[] = $key;
223
                $cacheKeysToIdMap[$key] = $id;
224
            }
225
        }
226
227
        // No in-memory misses
228
        if (empty($cacheKeys)) {
229
            $this->logger->logCacheHit($ids, 3, true);
230
231
            return $list;
232
        }
233
234
        // Load cache items by cache keys (will contain hits and misses)
235
        $loaded = [];
236
        $cacheMisses = [];
237
        foreach ($this->cache->getItems($cacheKeys) as $key => $cacheItem) {
238
            $id = $cacheKeysToIdMap[$key];
239
            if ($cacheItem->isHit()) {
240
                $list[$id] = $cacheItem->get();
241
                $loaded[$id] = $list[$id];
242
            } else {
243
                $cacheMisses[] = $id;
244
                $list[$id] = $cacheItem;
245
            }
246
        }
247
248
        // No cache pool misses, cache loaded items in-memory and return
249
        if (empty($cacheMisses)) {
250
            $this->logger->logCacheHit($ids, 3, true);
251
            $this->inMemory->setMulti($loaded, $cacheIndexes);
252
253
            return $list;
254
        }
255
256
        // Load missing items, save to cache & apply to list if found
257
        $backendLoadedList = $backendLoader($cacheMisses);
258
        foreach ($cacheMisses as $id) {
259
            if (isset($backendLoadedList[$id])) {
260
                $this->cache->save(
261
                    $list[$id]
262
                        ->set($backendLoadedList[$id])
263
                        ->tag($cacheTagger($backendLoadedList[$id]))
264
                );
265
                $loaded[$id] = $backendLoadedList[$id];
266
                $list[$id] = $backendLoadedList[$id];
267
            } else {
268
                // not found
269
                unset($list[$id]);
270
            }
271
        }
272
273
        $this->inMemory->setMulti($loaded, $cacheIndexes);
274
        unset($loaded, $backendLoadedList);
275
        $this->logger->logCacheMiss($cacheMisses, 3);
276
277
        return $list;
278
    }
279
280
    /**
281
     * Delete cache by keys.
282
     *
283
     * @param array $keys
284
     */
285
    final protected function deleteCache(array $keys): void
286
    {
287
        if (empty($keys)) {
288
            return;
289
        }
290
291
        $this->inMemory->deleteMulti($keys);
292
        $this->cache->deleteItems($keys);
293
    }
294
295
    /**
296
     * Invalidate cache by tags.
297
     *
298
     * @param array $tags
299
     */
300
    final protected function invalidateCache(array $tags): void
301
    {
302
        if (empty($tags)) {
303
            return;
304
        }
305
306
        $this->inMemory->clear();
307
        $this->cache->invalidateTags($tags);
308
    }
309
310
    /**
311
     * Clear ALL cache.
312
     *
313
     * Only use this in extname situations, or for isolation in tests.
314
     */
315
    final protected function clearCache(): void
316
    {
317
        $this->inMemory->clear();
318
        $this->cache->clear();
319
    }
320
}
321