Completed
Push — inmemory-pool-decoration ( 87319b...a526b7 )
by André
24:59
created

AbstractInMemoryHandler::init()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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