Completed
Push — inmemory-pool-decoration ( 469074...282551 )
by André
25:04
created

AbstractInMemoryHandler::setCacheDependencies()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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