Completed
Push — inmemory-pool-decoration ( 282551...25eaa5 )
by André
66:29 queued 45:47
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
use eZ\Publish\SPI\Persistence\Handler as PersistenceHandler;
12
13
/**
14
 * Class AbstractInMemoryHandler.
15
 *
16
 * Abstract handler for use in other Persistence Cache Handlers.
17
 */
18
abstract class AbstractInMemoryHandler
19
{
20
    /**
21
     * NOTE: Instance of this must be InMemoryClearingProxyAdapter in order for cache clearing to affect in-memory cache.
22
     *
23
     * @var \eZ\Publish\Core\Persistence\Cache\Adapter\InMemoryClearingProxyAdapter
24
     */
25
    protected $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 \eZ\Publish\SPI\Persistence\Handler $persistenceHandler
46
     * @param \eZ\Publish\Core\Persistence\Cache\Adapter\InMemoryClearingProxyAdapter $cache
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
        PersistenceHandler $persistenceHandler,
52
        InMemoryClearingProxyAdapter $cache,
53
        PersistenceLogger $logger,
54
        InMemoryCache $inMemory
55
    ) {
56
        $this->persistenceHandler = $persistenceHandler;
57
        $this->setCacheDependencies($cache, $logger, $inMemory);
58
        $this->init();
59
    }
60
61
    /**
62
     * @api May be used from handlers that needs to inject something else then PersistenceHandler to __construct(), to
63
     *      avoid having to have intimate knowledge of internal classes used by AbstractInMemoryHandler in signature.
64
     *
65
     * E.g:
66
     *    public function __construct(MyHandler $myHandler, ...$params) {
67
     *        $this->myHandler = $myHandler;
68
     *        $this->setCacheDependencies(...$params);
69
     *    }
70
     *
71
     * @param \eZ\Publish\Core\Persistence\Cache\Adapter\InMemoryClearingProxyAdapter $cache
72
     * @param \eZ\Publish\Core\Persistence\Cache\PersistenceLogger $logger
73
     * @param \eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache $inMemory
74
     */
75
    protected function setCacheDependencies(
76
        InMemoryClearingProxyAdapter $cache,
77
        PersistenceLogger $logger,
78
        InMemoryCache $inMemory
79
    ) {
80
        $this->cache = $cache;
81
        $this->logger = $logger;
82
        $this->inMemory = $inMemory;
83
    }
84
85
    /**
86
     * Optional function to initialize handler without having to overload __construct().
87
     */
88
    protected function init(): void
89
    {
90
        // overload to add init logic if needed in handler
91
    }
92
93
    /**
94
     * Load one cache item from cache and loggs the hits / misses.
95
     *
96
     * Load items from in-memory cache, symfony cache pool or backend in that order.
97
     * If not cached the returned objects will be placed in cache.
98
     *
99
     * @param int|string $id
100
     * @param string $keyPrefix E.g "ez-content-"
101
     * @param callable $backendLoader Function for loading missing objects, gets array with missing id's as argument,
102
     *                                expects return value to be array with id as key. Missing items should be missing.
103
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
104
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
105
     * @param string $keySuffix Optional, e.g "-by-identifier"
106
     *
107
     * @return object
108
     */
109
    final protected function getCacheValue(
110
        $id,
111
        string $keyPrefix,
112
        callable $backendLoader,
113
        callable $cacheTagger,
114
        callable $cacheIndexes,
115
        string $keySuffix = ''
116
    ) {
117
        $key = $keyPrefix . $id . $keySuffix;
118
        // In-memory
119
        if ($object = $this->inMemory->get($key)) {
120
            $this->logger->logCacheHit([$id], 3, true);
121
122
            return $object;
123
        }
124
125
        // Cache pool
126
        $cacheItem = $this->cache->getItem($key);
127 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...
128
            $this->logger->logCacheHit([$id], 3);
129
            $this->inMemory->setMulti([$object = $cacheItem->get()], $cacheIndexes);
130
131
            return $object;
132
        }
133
134
        // Backend
135
        $object = $backendLoader($id);
136
        $this->inMemory->setMulti([$object], $cacheIndexes);
137
        $this->logger->logCacheMiss([$id], 3);
138
        $this->cache->save(
139
            $cacheItem
140
                ->set($object)
141
                ->tag($cacheTagger($object))
142
        );
143
144
        return $object;
145
    }
146
147
    /**
148
     * Load list of objects of some type and loggs the hits / misses.
149
     *
150
     * Load items from in-memory cache, symfony cache pool or backend in that order.
151
     * If not cached the returned objects will be placed in cache.
152
     *
153
     * @param string $key
154
     * @param callable $backendLoader Function for loading ALL objects, value is cached as-is.
155
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
156
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
157
     * @param callable $listTags Optional, global tags for the list cache.
158
     * @param array $arguments Optional, arguments when parnt method takes arguments that key varies on.
159
     *
160
     * @return array
161
     */
162
    final protected function getListCacheValue(
163
        string $key,
164
        callable $backendLoader,
165
        callable $cacheTagger,
166
        callable $cacheIndexes,
167
        callable $listTags = null,
168
        array $arguments = []
169
    ) {
170
        // In-memory
171
        if ($objects = $this->inMemory->get($key)) {
172
            $this->logger->logCacheHit($arguments, 3, true);
173
174
            return $objects;
175
        }
176
177
        // Cache pool
178
        $cacheItem = $this->cache->getItem($key);
179 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...
180
            $this->logger->logCacheHit($arguments, 3);
181
            $this->inMemory->setMulti($objects = $cacheItem->get(), $cacheIndexes, $key);
182
183
            return $objects;
184
        }
185
186
        // Backend
187
        $objects = $backendLoader();
188
        $this->inMemory->setMulti($objects, $cacheIndexes, $key);
189
        $this->logger->logCacheMiss($arguments, 3);
190
191
        if ($listTags !== null) {
192
            $tagSet = [$listTags()];
193
        } else {
194
            $tagSet = [[]];
195
        }
196
197
        foreach ($objects as $object) {
198
            $tagSet[] = $cacheTagger($object);
199
        }
200
201
        $this->cache->save(
202
            $cacheItem
203
                ->set($objects)
204
                ->tag(array_unique(array_merge(...$tagSet)))
205
        );
206
207
        return $objects;
208
    }
209
210
    /**
211
     * Load several cache items from cache and loggs the hits / misses.
212
     *
213
     * Load items from in-memory cache, symfony cache pool or backend in that order.
214
     * If not cached the returned objects will be placed in cache.
215
     *
216
     * Cache items must be stored with a key in the following format "${keyPrefix}${id}", like "ez-content-info-${id}",
217
     * in order for this method to be able to prefix key on id's and also extract key prefix afterwards.
218
     *
219
     * @param array $ids
220
     * @param string $keyPrefix E.g "ez-content-"
221
     * @param callable $backendLoader Function for loading missing objects, gets array with missing id's as argument,
222
     *                                expects return value to be array with id as key. Missing items should be missing.
223
     * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
224
     * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
225
     * @param string $keySuffix Optional, e.g "-by-identifier"
226
     *
227
     * @return array
228
     */
229
    final protected function getMultipleCacheValues(
230
        array $ids,
231
        string $keyPrefix,
232
        callable $backendLoader,
233
        callable $cacheTagger,
234
        callable $cacheIndexes,
235
        string $keySuffix = ''
236
    ): array {
237
        if (empty($ids)) {
238
            return [];
239
        }
240
241
        // Generate unique cache keys and check if in-memory
242
        $list = [];
243
        $cacheKeys = [];
244
        $cacheKeysToIdMap = [];
245
        foreach (array_unique($ids) as $id) {
246
            $key = $keyPrefix . $id . $keySuffix;
247
            if ($object = $this->inMemory->get($key)) {
248
                $list[$id] = $object;
249
            } else {
250
                $cacheKeys[] = $key;
251
                $cacheKeysToIdMap[$key] = $id;
252
            }
253
        }
254
255
        // No in-memory misses
256
        if (empty($cacheKeys)) {
257
            $this->logger->logCacheHit($ids, 3, true);
258
259
            return $list;
260
        }
261
262
        // Load cache items by cache keys (will contain hits and misses)
263
        $loaded = [];
264
        $cacheMisses = [];
265
        foreach ($this->cache->getItems($cacheKeys) as $key => $cacheItem) {
266
            $id = $cacheKeysToIdMap[$key];
267
            if ($cacheItem->isHit()) {
268
                $list[$id] = $cacheItem->get();
269
                $loaded[$id] = $list[$id];
270
            } else {
271
                $cacheMisses[] = $id;
272
                $list[$id] = $cacheItem;
273
            }
274
        }
275
276
        // No cache pool misses, cache loaded items in-memory and return
277
        if (empty($cacheMisses)) {
278
            $this->logger->logCacheHit($ids, 3);
279
            $this->inMemory->setMulti($loaded, $cacheIndexes);
280
281
            return $list;
282
        }
283
284
        // Load missing items, save to cache & apply to list if found
285
        $backendLoadedList = $backendLoader($cacheMisses);
286
        foreach ($cacheMisses as $id) {
287
            if (isset($backendLoadedList[$id])) {
288
                $this->cache->save(
289
                    $list[$id]
290
                        ->set($backendLoadedList[$id])
291
                        ->tag($cacheTagger($backendLoadedList[$id]))
292
                );
293
                $loaded[$id] = $backendLoadedList[$id];
294
                $list[$id] = $backendLoadedList[$id];
295
            } else {
296
                // not found
297
                unset($list[$id]);
298
            }
299
        }
300
301
        $this->inMemory->setMulti($loaded, $cacheIndexes);
302
        unset($loaded, $backendLoadedList);
303
        $this->logger->logCacheMiss($cacheMisses, 3);
304
305
        return $list;
306
    }
307
}
308