Completed
Push — EZP-30503 ( c32d12...186ae2 )
by
unknown
27:48
created

AbstractInMemoryHandler::getMultipleCacheValues()   C

Complexity

Conditions 13
Paths 40

Size

Total Lines 80

Duplication

Lines 6
Ratio 7.5 %

Importance

Changes 0
Metric Value
cc 13
nc 40
nop 7
dl 6
loc 80
rs 5.7295
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
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
5
 * @license For full copyright and license information view LICENSE file distributed with this source code.
6
 */
7
namespace eZ\Publish\Core\Persistence\Cache;
8
9
use eZ\Publish\Core\Persistence\Cache\Adapter\InMemoryClearingProxyAdapter;
10
use eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache;
11
12
/**
13
 * Abstract handler for use in other SPI Handlers.
14
 *
15
 * @internal Can be used in external handlers, but be aware this should be regarded as experimental feature.
16
 *           As in, method signatures and behaviour might change in the future.
17
 */
18
abstract class AbstractInMemoryHandler
19
{
20
    /**
21
     * NOTE: Instance of this must be InMemoryClearingProxyAdapter in order for cache clearing to affect in-memory cache.
22
     *
23
     * @var \eZ\Publish\Core\Persistence\Cache\Adapter\InMemoryClearingProxyAdapter
24
     */
25
    protected $cache;
26
27
    /**
28
     * @var \eZ\Publish\Core\Persistence\Cache\PersistenceLogger
29
     */
30
    protected $logger;
31
32
    /**
33
     * NOTE: On purpose private as it's only supposed to be interacted with in tandem with symfony cache here,
34
     *       hence the cache decorator and the reusable methods here.
35
     *
36
     * @var \eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache
37
     */
38
    private $inMemory;
39
40
    /**
41
     * Setups current handler with everything needed.
42
     *
43
     * @param \eZ\Publish\Core\Persistence\Cache\Adapter\InMemoryClearingProxyAdapter $cache
44
     * @param \eZ\Publish\Core\Persistence\Cache\PersistenceLogger $logger
45
     * @param \eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache $inMemory
46
     */
47
    public function __construct(
48
        InMemoryClearingProxyAdapter $cache,
49
        PersistenceLogger $logger,
50
        InMemoryCache $inMemory
51
    ) {
52
        $this->cache = $cache;
53
        $this->logger = $logger;
54
        $this->inMemory = $inMemory;
55
    }
56
57
    /**
58
     * Load one cache item from cache and loggs the hits / misses.
59
     *
60
     * Load items from in-memory cache, symfony cache pool or backend in that order.
61
     * If not cached the returned objects will be placed in cache.
62
     *
63
     * @param int|string $id
64
     * @param string $keyPrefix E.g "ez-content-"
65
     * @param callable $backendLoader Function for loading missing objects, gets array with missing id's as argument,
66
     *                                expects return value to be array with id as key. Missing items should be missing.
67
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
68
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
69
     * @param string $keySuffix Optional, e.g "-by-identifier"
70
     *
71
     * @return object
72
     */
73
    final protected function getCacheValue(
74
        $id,
75
        string $keyPrefix,
76
        callable $backendLoader,
77
        callable $cacheTagger,
78
        callable $cacheIndexes,
79
        string $keySuffix = '',
80
        array $arguments = []
81
    ) {
82
        $key = $keyPrefix . $id . $keySuffix;
83
        // In-memory
84 View Code Duplication
        if ($object = $this->inMemory->get($key)) {
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...
85
            $this->logger->logCacheHit($arguments ?: [$id], 3, true);
86
87
            return $object;
88
        }
89
90
        // Cache pool
91
        $cacheItem = $this->cache->getItem($key);
92
        if ($cacheItem->isHit()) {
93
            $this->logger->logCacheHit($arguments ?: [$id], 3);
94
            $this->inMemory->setMulti([$object = $cacheItem->get()], $cacheIndexes);
95
96
            return $object;
97
        }
98
99
        // Backend
100
        // (log misses first in case of $backendLoader ends up throwing NotFound)
101
        $this->logger->logCacheMiss($arguments ?: [$id], 3);
102
103
        $object = $backendLoader($id);
104
        $this->inMemory->setMulti([$object], $cacheIndexes);
105
        $this->cache->save(
106
            $cacheItem
107
                ->set($object)
108
                ->tag($cacheTagger($object))
109
        );
110
111
        return $object;
112
    }
113
114
    /**
115
     * Load list of objects of some type and loggs the hits / misses.
116
     *
117
     * Load items from in-memory cache, symfony cache pool or backend in that order.
118
     * If not cached the returned objects will be placed in cache.
119
     *
120
     * @param string $key
121
     * @param callable $backendLoader Function for loading ALL objects, value is cached as-is.
122
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
123
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
124
     * @param callable $listTags Optional, global tags for the list cache.
125
     * @param array $arguments Optional, arguments when parnt method takes arguments that key varies on.
126
     *
127
     * @return array
128
     */
129
    final protected function getListCacheValue(
130
        string $key,
131
        callable $backendLoader,
132
        callable $cacheTagger,
133
        callable $cacheIndexes,
134
        callable $listTags = null,
135
        array $arguments = []
136
    ) {
137
        // In-memory
138 View Code Duplication
        if ($objects = $this->inMemory->get($key)) {
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...
139
            $this->logger->logCacheHit($arguments, 3, true);
140
141
            return $objects;
142
        }
143
144
        // Cache pool
145
        $cacheItem = $this->cache->getItem($key);
146 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...
147
            $this->logger->logCacheHit($arguments, 3);
148
            $this->inMemory->setMulti($objects = $cacheItem->get(), $cacheIndexes, $key);
149
150
            return $objects;
151
        }
152
153
        // Backend
154
        // (log misses first in case of $backendLoader ends up throwing NotFound)
155
        $this->logger->logCacheMiss($arguments, 3);
156
157
        $objects = $backendLoader();
158
        $this->inMemory->setMulti($objects, $cacheIndexes, $key);
159
        if ($listTags !== null) {
160
            $tagSet = [$listTags()];
161
        } else {
162
            $tagSet = [[]];
163
        }
164
165
        foreach ($objects as $object) {
166
            $tagSet[] = $cacheTagger($object);
167
        }
168
169
        $this->cache->save(
170
            $cacheItem
171
                ->set($objects)
172
                ->tag(array_unique(array_merge(...$tagSet)))
173
        );
174
175
        return $objects;
176
    }
177
178
    /**
179
     * Load several cache items from cache and loggs the hits / misses.
180
     *
181
     * Load items from in-memory cache, symfony cache pool or backend in that order.
182
     * If not cached the returned objects will be placed in cache.
183
     *
184
     * Cache items must be stored with a key in the following format "${keyPrefix}${id}", like "ez-content-info-${id}",
185
     * in order for this method to be able to prefix key on id's and also extract key prefix afterwards.
186
     *
187
     * @param array $ids
188
     * @param string $keyPrefix E.g "ez-content-"
189
     * @param callable $backendLoader Function for loading missing objects, gets array with missing id's as argument,
190
     *                                expects return value to be array with id as key. Missing items should be missing.
191
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
192
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
193
     * @param string $keySuffix Optional, e.g "-by-identifier"
194
     *
195
     * @return array
196
     */
197
    final protected function getMultipleCacheValues(
198
        array $ids,
199
        string $keyPrefix,
200
        callable $backendLoader,
201
        callable $cacheTagger,
202
        callable $cacheIndexes,
203
        string $keySuffix = '',
204
        array $arguments = []
205
    ): array {
206
        if (empty($ids)) {
207
            return [];
208
        }
209
210
        // Generate unique cache keys and check if in-memory
211
        $list = [];
212
        $cacheKeys = [];
213
        $cacheKeysToIdMap = [];
214
        foreach (array_unique($ids) as $id) {
215
            $key = $keyPrefix . $id . $keySuffix;
216
            if ($object = $this->inMemory->get($key)) {
217
                $list[$id] = $object;
218
            } else {
219
                $cacheKeys[] = $key;
220
                $cacheKeysToIdMap[$key] = $id;
221
            }
222
        }
223
224
        // No in-memory misses
225
        if (empty($cacheKeys)) {
226
            $this->logger->logCacheHit($arguments ?: $ids, 3, true);
227
228
            return $list;
229
        }
230
231
        // Load cache items by cache keys (will contain hits and misses)
232
        $loaded = [];
233
        $cacheMisses = [];
234
        foreach ($this->cache->getItems($cacheKeys) as $key => $cacheItem) {
235
            $id = $cacheKeysToIdMap[$key];
236
            if ($cacheItem->isHit()) {
237
                $list[$id] = $cacheItem->get();
238
                $loaded[$id] = $list[$id];
239
            } else {
240
                $cacheMisses[] = $id;
241
                $list[$id] = $cacheItem;
242
            }
243
        }
244
245
        // No cache pool misses, cache loaded items in-memory and return
246 View Code Duplication
        if (empty($cacheMisses)) {
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...
247
            $this->logger->logCacheHit($arguments ?: $ids, 3);
248
            $this->inMemory->setMulti($loaded, $cacheIndexes);
249
250
            return $list;
251
        }
252
253
        // Load missing items, save to cache & apply to list if found
254
        $backendLoadedList = $backendLoader($cacheMisses);
255
        foreach ($cacheMisses as $id) {
256
            if (isset($backendLoadedList[$id])) {
257
                $this->cache->save(
258
                    $list[$id]
259
                        ->set($backendLoadedList[$id])
260
                        ->tag($cacheTagger($backendLoadedList[$id]))
261
                );
262
                $loaded[$id] = $backendLoadedList[$id];
263
                $list[$id] = $backendLoadedList[$id];
264
            } else {
265
                // not found
266
                unset($list[$id]);
267
            }
268
        }
269
270
        // Save cache & log miss (this method expects multi backend loader to return empty array over throwing NotFound)
271
        $this->inMemory->setMulti($loaded, $cacheIndexes);
272
        unset($loaded, $backendLoadedList);
273
        $this->logger->logCacheMiss($arguments ?: $cacheMisses, 3);
274
275
        return $list;
276
    }
277
278
    /**
279
     * Escape an argument for use in cache keys when needed.
280
     *
281
     * WARNING: Only use the result of this in cache keys, it won't work to use loading the item from backend on miss.
282
     *
283
     * @param string $identifier
284
     *
285
     * @return string
286
     */
287 View Code Duplication
    final protected function escapeForCacheKey(string $identifier)
288
    {
289
        return \str_replace(
290
            ['_', '/', ':', '(', ')', '@', '\\', '{', '}'],
291
            ['__', '_S', '_C', '_BO', '_BC', '_A', '_BS', '_CBO', '_CBC'],
292
            $identifier
293
        );
294
    }
295
}
296