Completed
Push — master ( 955b04...f00653 )
by André
38:12 queued 16:07
created

AbstractInMemoryHandler::getListCacheValue()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 47

Duplication

Lines 6
Ratio 12.77 %

Importance

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