Completed
Push — inmemory-abstract-trait ( 124d36 )
by André
26:24 queued 03:39
created

AbstractInMemoryHandler::getMultipleCacheValues()   B

Complexity

Conditions 7
Paths 13

Size

Total Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 13
nop 6
dl 0
loc 53
rs 8.0921
c 0
b 0
f 0

How to fix   Long Method   

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 AbstractInMemoryHandler.
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
    use AbstractTrait {
23
        getMultipleCacheValues as getMultipleCacheValuesWithoutInMemoryCache;
24
    }
25
26
    /**
27
     * NOTE: $cache and $inMemory is private in order to abstract interactions across both,
28
     *       and be able to optimize this later without affecting all classes extending this.
29
     *
30
     * @var \Symfony\Component\Cache\Adapter\TagAwareAdapterInterface
31
     */
32
    private $cache;
33
34
    /**
35
     * @var \eZ\Publish\SPI\Persistence\Handler
36
     */
37
    protected $persistenceHandler;
38
39
    /**
40
     * @var \eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache
41
     */
42
    private $inMemory;
43
44
    /**
45
     * Setups current handler with everything needed.
46
     *
47
     * @param \Symfony\Component\Cache\Adapter\TagAwareAdapterInterface $cache
48
     * @param \eZ\Publish\SPI\Persistence\Handler $persistenceHandler
49
     * @param \eZ\Publish\Core\Persistence\Cache\PersistenceLogger $logger
50
     * @param \eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache $inMemory
51
     */
52
    public function __construct(
53
        TagAwareAdapterInterface $cache,
54
        PersistenceHandler $persistenceHandler,
55
        PersistenceLogger $logger,
56
        InMemoryCache $inMemory
57
    ) {
58
        $this->cache = $cache;
59
        $this->persistenceHandler = $persistenceHandler;
60
        $this->logger = $logger;
61
        $this->inMemory = $inMemory;
62
63
        $this->init();
64
    }
65
66
    /**
67
     * Function to initialize handler without having to overload __construct().
68
     */
69
    abstract protected function init(): void;
70
71
    /**
72
     * Load one cache item from cache and loggs the hits / misses.
73
     *
74
     * Load items from in-memory cache, symfony cache pool or backend in that order.
75
     * If not cached the returned objects will be placed in cache.
76
     *
77
     * @param int|string $id
78
     * @param string $keyPrefix E.g "ez-content-"
79
     * @param callable $backendLoader Function for loading missing objects, gets array with missing id's as argument,
80
     *                                expects return value to be array with id as key. Missing items should be missing.
81
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
82
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
83
     * @param string $keySuffix Optional, e.g "-by-identifier"
84
     *
85
     * @return object
86
     */
87
    final protected function getCacheValue(
88
        $id,
89
        string $keyPrefix,
90
        callable $backendLoader,
91
        callable $cacheTagger,
92
        callable $cacheIndexes,
93
        string $keySuffix = ''
94
    ) {
95
        $key = $keyPrefix . $id . $keySuffix;
96
        // In-memory
97
        if ($object = $this->inMemory->get($key)) {
98
            $this->logger->logCacheHit([$id], 3, true);
99
100
            return $object;
101
        }
102
103
        // Cache pool
104
        $cacheItem = $this->cache->getItem($key);
105 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...
106
            $this->logger->logCacheHit([$id], 3);
107
            $this->inMemory->setMulti([$object = $cacheItem->get()], $cacheIndexes);
108
109
            return $object;
110
        }
111
112
        // Backend
113
        $object = $backendLoader($id);
114
        $this->inMemory->setMulti([$object], $cacheIndexes);
115
        $this->logger->logCacheMiss([$id], 3);
116
        $this->cache->save(
117
            $cacheItem
118
                ->set($object)
119
                ->tag($cacheTagger($object))
120
        );
121
122
        return $object;
123
    }
124
125
    /**
126
     * Load list of objects of some type and loggs the hits / misses.
127
     *
128
     * Load items from in-memory cache, symfony cache pool or backend in that order.
129
     * If not cached the returned objects will be placed in cache.
130
     *
131
     * @param string $key
132
     * @param callable $backendLoader Function for loading ALL objects, value is cached as-is.
133
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
134
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
135
     * @param callable $listTags Optional, global tags for the list cache.
136
     * @param array $arguments Optional, arguments when parnt method takes arguments that key varies on.
137
     *
138
     * @return object
139
     */
140
    final protected function getListCacheValue(
141
        string $key,
142
        callable $backendLoader,
143
        callable $cacheTagger,
144
        callable $cacheIndexes,
145
        callable $listTags = null,
146
        array $arguments = []
147
    ) {
148
        // In-memory
149
        if ($objects = $this->inMemory->get($key)) {
150
            $this->logger->logCacheHit($arguments, 3, true);
151
152
            return $objects;
153
        }
154
155
        // Cache pool
156
        $cacheItem = $this->cache->getItem($key);
157 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...
158
            $this->logger->logCacheHit($arguments, 3);
159
            $this->inMemory->setMulti($objects = $cacheItem->get(), $cacheIndexes, $key);
160
161
            return $objects;
162
        }
163
164
        // Backend
165
        $objects = $backendLoader();
166
        $this->inMemory->setMulti($objects, $cacheIndexes, $key);
167
        $this->logger->logCacheMiss($arguments, 3);
168
169
        if ($listTags !== null) {
170
            $tagSet = [$listTags()];
171
        } else {
172
            $tagSet = [[]];
173
        }
174
175
        foreach ($objects as $object) {
176
            $tagSet[] = $cacheTagger($object);
177
        }
178
179
        $this->cache->save(
180
            $cacheItem
181
                ->set($objects)
182
                ->tag(array_unique(array_merge(...$tagSet)))
183
        );
184
185
        return $objects;
186
    }
187
188
    /**
189
     * Load several cache items from cache and loggs the hits / misses.
190
     *
191
     * Load items from in-memory cache, symfony cache pool or backend in that order.
192
     * If not cached the returned objects will be placed in cache.
193
     *
194
     * Cache items must be stored with a key in the following format "${keyPrefix}${id}", like "ez-content-info-${id}",
195
     * in order for this method to be able to prefix key on id's and also extract key prefix afterwards.
196
     *
197
     * @param array $ids
198
     * @param string $keyPrefix E.g "ez-content-"
199
     * @param callable $backendLoader Function for loading missing objects, gets array with missing id's as argument,
200
     *                                expects return value to be array with id as key. Missing items should be missing.
201
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
202
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
203
     * @param string $keySuffix Optional, e.g "-by-identifier"
204
     *
205
     * @return array
206
     */
207
    final protected function getMultipleCacheValues(
208
        array $ids,
209
        string $keyPrefix,
210
        callable $backendLoader,
211
        callable $cacheTagger,
212
        callable $cacheIndexes,
213
        string $keySuffix = ''
214
    ): array {
215
        if (empty($ids)) {
216
            return [];
217
        }
218
219
        // Generate unique cache keys and check if in-memory
220
        $list = [];
221
        foreach (\array_unique($ids) as $id) {
222
            if ($object = $this->inMemory->get($keyPrefix . $id . $keySuffix)) {
223
                $list[$id] = $object;
224
            } else {
225
                $list[$id] = null;
226
                $misses[] = $id;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$misses was never initialized. Although not strictly required by PHP, it is generally a good practice to add $misses = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
227
            }
228
        }
229
230
        // No in-memory misses
231
        if (empty($misses)) {
232
            $this->logger->logCacheHit($ids, 3, true);
233
234
            return $list;
235
        }
236
237
        $loaded = $this->getMultipleCacheValuesWithoutInMemoryCache(
238
            $misses,
239
            $keyPrefix,
240
            $backendLoader,
241
            $cacheTagger,
242
            $keySuffix
243
        );
244
245
        foreach ($misses as $id) {
246
            if (isset($loaded[$id])) {
247
                $list[$id] = $loaded[$id];
248
            } else {
249
                // not found
250
                unset($list[$id]);
251
            }
252
        }
253
254
        // save cache to memory
255
        $this->inMemory->setMulti($loaded, $cacheIndexes);
256
        unset($loaded);
257
258
        return $list;
259
    }
260
261
    /**
262
     * Delete cache by keys.
263
     *
264
     * @param array $keys
265
     */
266
    final protected function deleteCache(array $keys): void
267
    {
268
        if (empty($keys)) {
269
            return;
270
        }
271
272
        $this->inMemory->deleteMulti($keys);
273
        $this->cache->deleteItems($keys);
274
    }
275
276
    /**
277
     * Invalidate cache by tags.
278
     *
279
     * @param array $tags
280
     */
281
    final protected function invalidateCache(array $tags): void
282
    {
283
        if (empty($tags)) {
284
            return;
285
        }
286
287
        $this->inMemory->clear();
288
        $this->cache->invalidateTags($tags);
289
    }
290
291
    /**
292
     * Clear ALL cache.
293
     *
294
     * Only use this in extname situations, or for isolation in tests.
295
     */
296
    final protected function clearCache(): void
297
    {
298
        $this->inMemory->clear();
299
        $this->cache->clear();
300
    }
301
}
302