Completed
Push — in-memory-cache2 ( de4787...b8bac7 )
by André
24:03
created

AbstractInMemoryHandler   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 283
Duplicated Lines 4.24 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
dl 12
loc 283
rs 10
c 0
b 0
f 0
wmc 23
lcom 1
cbo 4

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A getCacheValue() 6 35 3
A getListCacheValue() 6 41 4
C getMultipleCacheValues() 0 81 12
A deleteCache() 0 5 1
A invalidateCache() 0 5 1
A clearCache() 0 5 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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
     * @var \Symfony\Component\Cache\Adapter\TagAwareAdapterInterface
24
     */
25
    private $cache;
26
27
    /**
28
     * @var \eZ\Publish\SPI\Persistence\Handler
29
     */
30
    protected $persistenceHandler;
31
32
    /**
33
     * @var \eZ\Publish\Core\Persistence\Cache\PersistenceLogger
34
     */
35
    protected $logger;
36
37
    /**
38
     * @var \eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache
39
     */
40
    private $inMemory;
41
42
    /**
43
     * Setups current handler with everything needed.
44
     *
45
     * @param \Symfony\Component\Cache\Adapter\TagAwareAdapterInterface $cache
46
     * @param \eZ\Publish\SPI\Persistence\Handler $persistenceHandler
47
     * @param \eZ\Publish\Core\Persistence\Cache\PersistenceLogger $logger
48
     * @param \eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache $inMemory
49
     */
50
    public function __construct(
51
        TagAwareAdapterInterface $cache,
52
        PersistenceHandler $persistenceHandler,
53
        PersistenceLogger $logger,
54
        InMemoryCache $inMemory
55
    ) {
56
        $this->cache = $cache;
57
        $this->persistenceHandler = $persistenceHandler;
58
        $this->logger = $logger;
59
        $this->inMemory = $inMemory;
60
    }
61
62
    /**
63
     * Load one cache item from cache and loggs the hits / misses.
64
     *
65
     * Load items from in-memory cache, symfony cache pool or backend in that order.
66
     * If not cached the returned objects will be placed in cache.
67
     *
68
     * @param int|string $id
69
     * @param string $keyPrefix E.g "ez-content-"
70
     * @param callable $backendLoader Function for loading missing objects, gets array with missing id's as argument,
71
     *                                expects return value to be array with id as key. Missing items should be missing.
72
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
73
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
74
     *
75
     * @return object
76
     */
77
    final protected function getCacheValue(
78
        $id,
79
        string $keyPrefix,
80
        callable $backendLoader,
81
        callable $cacheTagger,
82
        callable $cacheIndexes
83
    ) {
84
        // In-memory
85
        if ($object = $this->inMemory->get($keyPrefix . $id)) {
86
            $this->logger->logCacheHit([$id], 3, true);
87
88
            return $object;
89
        }
90
91
        // Cache pool
92
        $cacheItem = $this->cache->getItem($keyPrefix . $id);
93 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...
94
            $this->logger->logCacheHit([$id], 3);
95
            $this->inMemory->setMulti([$object = $cacheItem->get()], $cacheIndexes);
96
97
            return $object;
98
        }
99
100
        // Backend
101
        $object = $backendLoader($id);
102
        $this->inMemory->setMulti([$object], $cacheIndexes);
103
        $this->logger->logCacheMiss([$id], 3);
104
        $this->cache->save(
105
            $cacheItem
106
                ->set($object)
107
                ->tag($cacheTagger($object))
108
        );
109
110
        return $object;
111
    }
112
113
    /**
114
     * Load list of objects of some type and loggs the hits / misses.
115
     *
116
     * Load items from in-memory cache, symfony cache pool or backend in that order.
117
     * If not cached the returned objects will be placed in cache.
118
     *
119
     * @param string $key
120
     * @param callable $backendLoader Function for loading ALL objects, value is cached as-is.
121
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
122
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
123
     * @param array $tags Optional global tags for the list cache.
124
     *
125
     * @return object
126
     */
127
    final protected function getListCacheValue(
128
        $key,
129
        callable $backendLoader,
130
        callable $cacheTagger,
131
        callable $cacheIndexes,
132
        array $tags = []
133
    ) {
134
        // In-memory
135
        if ($objects = $this->inMemory->get($key)) {
136
            $this->logger->logCacheHit([], 3, true);
137
138
            return $objects;
139
        }
140
141
        // Cache pool
142
        $cacheItem = $this->cache->getItem($key);
143 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...
144
            $this->logger->logCacheHit([], 3);
145
            $this->inMemory->setMulti([$objects = $cacheItem->get()], $cacheIndexes, $key);
146
147
            return $objects;
148
        }
149
150
        // Backend
151
        $objects = $backendLoader();
152
        $this->inMemory->setMulti($objects, $cacheIndexes, $key);
153
        $this->logger->logCacheMiss([], 3);
154
155
        $tags = [$tags];
156
        foreach ($objects as $object) {
157
            $tags[] = $cacheTagger($object);
158
        }
159
160
        $this->cache->save(
161
            $cacheItem
162
                ->set($objects)
163
                ->tag(array_unique(array_merge(...$tags)))
164
        );
165
166
        return $objects;
167
    }
168
169
170
    /**
171
     * Load several cache items from cache and loggs the hits / misses.
172
     *
173
     * Load items from in-memory cache, symfony cache pool or backend in that order.
174
     * If not cached the returned objects will be placed in cache.
175
     *
176
     * Cache items must be stored with a key in the following format "${keyPrefix}${id}", like "ez-content-info-${id}",
177
     * in order for this method to be able to prefix key on id's and also extract key prefix afterwards.
178
     *
179
     * @param array $ids
180
     * @param string $keyPrefix E.g "ez-content-"
181
     * @param callable $backendLoader Function for loading missing objects, gets array with missing id's as argument,
182
     *                                expects return value to be array with id as key. Missing items should be missing.
183
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
184
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
185
     *
186
     * @return array
187
     */
188
    final protected function getMultipleCacheValues(
189
        array $ids,
190
        string $keyPrefix,
191
        callable $backendLoader,
192
        callable $cacheTagger,
193
        callable $cacheIndexes
194
    ): array {
195
196
        if (empty($ids)) {
197
            return [];
198
        }
199
200
        // Generate unique cache keys and check if in-memory
201
        $list = [];
202
        $cacheKeys = [];
203
        foreach (array_unique($ids) as $id) {
204
            if ($object = $this->inMemory->get($keyPrefix . $id)) {
205
                $list[$id] = $object;
206
            } else {
207
                $cacheKeys[] = $keyPrefix . $id;
208
            }
209
        }
210
211
        if (!empty($list)) {
212
            $this->logger->logCacheHit(array_keys($list), 3, true);
213
        }
214
215
        // No in-memory misses
216
        if (empty($cacheKeys)) {
217
            return $list;
218
        }
219
220
        // Load cache items by cache keys (will contain hits and misses)
221
        $loaded = [];
222
        $cacheMisses = [];
223
        $keyPrefixLength = strlen($keyPrefix);
224
        foreach ($this->cache->getItems($cacheKeys) as $key => $cacheItem) {
225
            $id = substr($key, $keyPrefixLength);
226
            if ($cacheItem->isHit()) {
227
                $list[$id] = $cacheItem->get();
228
                $loaded[$id] = $list[$id];
229
            } else {
230
                $cacheMisses[] = $id;
231
                $list[$id] = $cacheItem;
232
            }
233
        }
234
235
        if (!empty($loaded)) {
236
            $this->logger->logCacheHit(array_keys($loaded), 3);
237
        }
238
239
        // No cache pool misses, cache loaded items in-memory and return
240
        if (empty($cacheMisses)) {
241
            $this->inMemory->setMulti($loaded, $cacheIndexes);
242
243
            return $list;
244
        }
245
246
        // Load missing items, save to cache & apply to list if found
247
        $backendLoadedList = $backendLoader($cacheMisses);
248
        foreach ($cacheMisses as $id) {
249
            if (isset($backendLoadedList[$id])) {
250
                $this->cache->save(
251
                    $list[$id]
252
                        ->set($backendLoadedList[$id])
253
                        ->tag($cacheTagger($backendLoadedList[$id]))
254
                );
255
                $loaded[$id] = $backendLoadedList[$id];
256
                $list[$id] = $backendLoadedList[$id];
257
            } else {
258
                // not found
259
                unset($list[$id]);
260
            }
261
        }
262
263
        $this->inMemory->setMulti($loaded, $cacheIndexes);
264
        unset($loaded, $backendLoadedList);
265
        $this->logger->logCacheMiss($cacheMisses, 3);
266
267
        return $list;
268
    }
269
270
    /**
271
     * Delete cache by keys.
272
     *
273
     * @param array $keys
274
     */
275
    final protected function deleteCache(array $keys): void
276
    {
277
        $this->inMemory->deleteMulti($keys);
278
        $this->cache->deleteItems($keys);
279
    }
280
281
    /**
282
     * Invalidate cache by tags.
283
     *
284
     * @param array $tags
285
     */
286
    final protected function invalidateCache(array $tags): void
287
    {
288
        $this->inMemory->clear();
289
        $this->cache->invalidateTags($tags);
290
    }
291
292
    /**
293
     * Clear ALL cache.
294
     *
295
     * Only use this in extname situations, or for isolation in tests.
296
     */
297
    final protected function clearCache(): void
298
    {
299
        $this->inMemory->clear();
300
        $this->cache->clear();
301
    }
302
}
303