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