Completed
Push — inmemory-pool-decoration ( e8b077 )
by André
17:42
created

AbstractInMemoryHandler::invalidateCache()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 9
rs 9.9666
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\Adapter\InMemoryClearingProxyAdapter;
12
use eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache;
13
use eZ\Publish\SPI\Persistence\Handler as PersistenceHandler;
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: Instance of this must be InMemoryClearingProxyAdapter in order for cache clearing to affect in-memory cache.
24
     *
25
     * @var \eZ\Publish\Core\Persistence\Cache\Adapter\InMemoryClearingProxyAdapter
26
     */
27
    protected $cache;
28
29
    /**
30
     * @var \eZ\Publish\SPI\Persistence\Handler
31
     */
32
    protected $persistenceHandler;
33
34
    /**
35
     * @var \eZ\Publish\Core\Persistence\Cache\PersistenceLogger
36
     */
37
    protected $logger;
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 \eZ\Publish\Core\Persistence\Cache\Adapter\InMemoryClearingProxyAdapter $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
        InMemoryClearingProxyAdapter $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
     * Optional dunction to initialize handler without having to overload __construct().
68
     */
69
    protected function init(): void
70
    {
71
        // overload to add init logic if needed in handler
72
    }
73
74
    /**
75
     * Load one cache item from cache and loggs the hits / misses.
76
     *
77
     * Load items from in-memory cache, symfony cache pool or backend in that order.
78
     * If not cached the returned objects will be placed in cache.
79
     *
80
     * @param int|string $id
81
     * @param string $keyPrefix E.g "ez-content-"
82
     * @param callable $backendLoader Function for loading missing objects, gets array with missing id's as argument,
83
     *                                expects return value to be array with id as key. Missing items should be missing.
84
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
85
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
86
     * @param string $keySuffix Optional, e.g "-by-identifier"
87
     *
88
     * @return object
89
     */
90
    final protected function getCacheValue(
91
        $id,
92
        string $keyPrefix,
93
        callable $backendLoader,
94
        callable $cacheTagger,
95
        callable $cacheIndexes,
96
        string $keySuffix = ''
97
    ) {
98
        $key = $keyPrefix . $id . $keySuffix;
99
        // In-memory
100
        if ($object = $this->inMemory->get($key)) {
101
            $this->logger->logCacheHit([$id], 3, true);
102
103
            return $object;
104
        }
105
106
        // Cache pool
107
        $cacheItem = $this->cache->getItem($key);
108 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...
109
            $this->logger->logCacheHit([$id], 3);
110
            $this->inMemory->setMulti([$object = $cacheItem->get()], $cacheIndexes);
111
112
            return $object;
113
        }
114
115
        // Backend
116
        $object = $backendLoader($id);
117
        $this->inMemory->setMulti([$object], $cacheIndexes);
118
        $this->logger->logCacheMiss([$id], 3);
119
        $this->cache->save(
120
            $cacheItem
121
                ->set($object)
122
                ->tag($cacheTagger($object))
123
        );
124
125
        return $object;
126
    }
127
128
    /**
129
     * Load list of objects of some type and loggs the hits / misses.
130
     *
131
     * Load items from in-memory cache, symfony cache pool or backend in that order.
132
     * If not cached the returned objects will be placed in cache.
133
     *
134
     * @param string $key
135
     * @param callable $backendLoader Function for loading ALL objects, value is cached as-is.
136
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
137
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
138
     * @param callable $listTags Optional, global tags for the list cache.
139
     * @param array $arguments Optional, arguments when parnt method takes arguments that key varies on.
140
     *
141
     * @return object
142
     */
143
    final protected function getListCacheValue(
144
        string $key,
145
        callable $backendLoader,
146
        callable $cacheTagger,
147
        callable $cacheIndexes,
148
        callable $listTags = null,
149
        array $arguments = []
150
    ) {
151
        // In-memory
152
        if ($objects = $this->inMemory->get($key)) {
153
            $this->logger->logCacheHit($arguments, 3, true);
154
155
            return $objects;
156
        }
157
158
        // Cache pool
159
        $cacheItem = $this->cache->getItem($key);
160 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...
161
            $this->logger->logCacheHit($arguments, 3);
162
            $this->inMemory->setMulti($objects = $cacheItem->get(), $cacheIndexes, $key);
163
164
            return $objects;
165
        }
166
167
        // Backend
168
        $objects = $backendLoader();
169
        $this->inMemory->setMulti($objects, $cacheIndexes, $key);
170
        $this->logger->logCacheMiss($arguments, 3);
171
172
        if ($listTags !== null) {
173
            $tagSet = [$listTags()];
174
        } else {
175
            $tagSet = [[]];
176
        }
177
178
        foreach ($objects as $object) {
179
            $tagSet[] = $cacheTagger($object);
180
        }
181
182
        $this->cache->save(
183
            $cacheItem
184
                ->set($objects)
185
                ->tag(array_unique(array_merge(...$tagSet)))
186
        );
187
188
        return $objects;
189
    }
190
191
    /**
192
     * Load several cache items from cache and loggs the hits / misses.
193
     *
194
     * Load items from in-memory cache, symfony cache pool or backend in that order.
195
     * If not cached the returned objects will be placed in cache.
196
     *
197
     * Cache items must be stored with a key in the following format "${keyPrefix}${id}", like "ez-content-info-${id}",
198
     * in order for this method to be able to prefix key on id's and also extract key prefix afterwards.
199
     *
200
     * @param array $ids
201
     * @param string $keyPrefix E.g "ez-content-"
202
     * @param callable $backendLoader Function for loading missing objects, gets array with missing id's as argument,
203
     *                                expects return value to be array with id as key. Missing items should be missing.
204
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
205
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
206
     * @param string $keySuffix Optional, e.g "-by-identifier"
207
     *
208
     * @return array
209
     */
210
    final protected function getMultipleCacheValues(
211
        array $ids,
212
        string $keyPrefix,
213
        callable $backendLoader,
214
        callable $cacheTagger,
215
        callable $cacheIndexes,
216
        string $keySuffix = ''
217
    ): array {
218
        if (empty($ids)) {
219
            return [];
220
        }
221
222
        // Generate unique cache keys and check if in-memory
223
        $list = [];
224
        $cacheKeys = [];
225
        $cacheKeysToIdMap = [];
226
        foreach (array_unique($ids) as $id) {
227
            $key = $keyPrefix . $id . $keySuffix;
228
            if ($object = $this->inMemory->get($key)) {
229
                $list[$id] = $object;
230
            } else {
231
                $cacheKeys[] = $key;
232
                $cacheKeysToIdMap[$key] = $id;
233
            }
234
        }
235
236
        // No in-memory misses
237
        if (empty($cacheKeys)) {
238
            $this->logger->logCacheHit($ids, 3, true);
239
240
            return $list;
241
        }
242
243
        // Load cache items by cache keys (will contain hits and misses)
244
        $loaded = [];
245
        $cacheMisses = [];
246
        foreach ($this->cache->getItems($cacheKeys) as $key => $cacheItem) {
247
            $id = $cacheKeysToIdMap[$key];
248
            if ($cacheItem->isHit()) {
249
                $list[$id] = $cacheItem->get();
250
                $loaded[$id] = $list[$id];
251
            } else {
252
                $cacheMisses[] = $id;
253
                $list[$id] = $cacheItem;
254
            }
255
        }
256
257
        // No cache pool misses, cache loaded items in-memory and return
258
        if (empty($cacheMisses)) {
259
            $this->logger->logCacheHit($ids, 3);
260
            $this->inMemory->setMulti($loaded, $cacheIndexes);
261
262
            return $list;
263
        }
264
265
        // Load missing items, save to cache & apply to list if found
266
        $backendLoadedList = $backendLoader($cacheMisses);
267
        foreach ($cacheMisses as $id) {
268
            if (isset($backendLoadedList[$id])) {
269
                $this->cache->save(
270
                    $list[$id]
271
                        ->set($backendLoadedList[$id])
272
                        ->tag($cacheTagger($backendLoadedList[$id]))
273
                );
274
                $loaded[$id] = $backendLoadedList[$id];
275
                $list[$id] = $backendLoadedList[$id];
276
            } else {
277
                // not found
278
                unset($list[$id]);
279
            }
280
        }
281
282
        $this->inMemory->setMulti($loaded, $cacheIndexes);
283
        unset($loaded, $backendLoadedList);
284
        $this->logger->logCacheMiss($cacheMisses, 3);
285
286
        return $list;
287
    }
288
}
289