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\InMemory\InMemoryCache; |
12
|
|
|
use eZ\Publish\SPI\Persistence\Handler as PersistenceHandler; |
13
|
|
|
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface; |
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: $cache and $inMemory is private in order to abstract interactions across both, |
24
|
|
|
* and be able to optimize this later without affecting all classes extending this. |
25
|
|
|
* |
26
|
|
|
* @var \Symfony\Component\Cache\Adapter\TagAwareAdapterInterface |
27
|
|
|
*/ |
28
|
|
|
private $cache; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var \eZ\Publish\SPI\Persistence\Handler |
32
|
|
|
*/ |
33
|
|
|
protected $persistenceHandler; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var \eZ\Publish\Core\Persistence\Cache\PersistenceLogger |
37
|
|
|
*/ |
38
|
|
|
protected $logger; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var \eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache |
42
|
|
|
*/ |
43
|
|
|
private $inMemory; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Setups current handler with everything needed. |
47
|
|
|
* |
48
|
|
|
* @param \Symfony\Component\Cache\Adapter\TagAwareAdapterInterface $cache |
49
|
|
|
* @param \eZ\Publish\SPI\Persistence\Handler $persistenceHandler |
50
|
|
|
* @param \eZ\Publish\Core\Persistence\Cache\PersistenceLogger $logger |
51
|
|
|
* @param \eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache $inMemory |
52
|
|
|
*/ |
53
|
|
|
public function __construct( |
54
|
|
|
TagAwareAdapterInterface $cache, |
55
|
|
|
PersistenceHandler $persistenceHandler, |
56
|
|
|
PersistenceLogger $logger, |
57
|
|
|
InMemoryCache $inMemory |
58
|
|
|
) { |
59
|
|
|
$this->cache = $cache; |
60
|
|
|
$this->persistenceHandler = $persistenceHandler; |
61
|
|
|
$this->logger = $logger; |
62
|
|
|
$this->inMemory = $inMemory; |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Load one cache item from cache and loggs the hits / misses. |
67
|
|
|
* |
68
|
|
|
* Load items from in-memory cache, symfony cache pool or backend in that order. |
69
|
|
|
* If not cached the returned objects will be placed in cache. |
70
|
|
|
* |
71
|
|
|
* @param int|string $id |
72
|
|
|
* @param string $keyPrefix E.g "ez-content-" |
73
|
|
|
* @param callable $backendLoader Function for loading missing objects, gets array with missing id's as argument, |
74
|
|
|
* expects return value to be array with id as key. Missing items should be missing. |
75
|
|
|
* @param callable $cacheTagger Gets cache object as argument, return array of cache tags. |
76
|
|
|
* @param callable $cacheIndexes Gets cache object as argument, return array of cache keys. |
77
|
|
|
* |
78
|
|
|
* @return object |
79
|
|
|
*/ |
80
|
|
|
final protected function getCacheValue( |
81
|
|
|
$id, |
82
|
|
|
string $keyPrefix, |
83
|
|
|
callable $backendLoader, |
84
|
|
|
callable $cacheTagger, |
85
|
|
|
callable $cacheIndexes, |
86
|
|
|
string $keyPostfix = '' |
87
|
|
|
) { |
88
|
|
|
$key = $keyPrefix . $id . $keyPostfix; |
89
|
|
|
// In-memory |
90
|
|
|
if ($object = $this->inMemory->get($key)) { |
91
|
|
|
$this->logger->logCacheHit([$id], 3, true); |
92
|
|
|
|
93
|
|
|
return $object; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
// Cache pool |
97
|
|
|
$cacheItem = $this->cache->getItem($key); |
98
|
|
View Code Duplication |
if ($cacheItem->isHit()) { |
|
|
|
|
99
|
|
|
$this->logger->logCacheHit([$id], 3); |
100
|
|
|
$this->inMemory->setMulti([$object = $cacheItem->get()], $cacheIndexes); |
101
|
|
|
|
102
|
|
|
return $object; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
// Backend |
106
|
|
|
$object = $backendLoader($id); |
107
|
|
|
$this->inMemory->setMulti([$object], $cacheIndexes); |
108
|
|
|
$this->logger->logCacheMiss([$id], 3); |
109
|
|
|
$this->cache->save( |
110
|
|
|
$cacheItem |
111
|
|
|
->set($object) |
112
|
|
|
->tag($cacheTagger($object)) |
113
|
|
|
); |
114
|
|
|
|
115
|
|
|
return $object; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Load list of objects of some type and loggs the hits / misses. |
120
|
|
|
* |
121
|
|
|
* Load items from in-memory cache, symfony cache pool or backend in that order. |
122
|
|
|
* If not cached the returned objects will be placed in cache. |
123
|
|
|
* |
124
|
|
|
* @param string $key |
125
|
|
|
* @param callable $backendLoader Function for loading ALL objects, value is cached as-is. |
126
|
|
|
* @param callable $cacheTagger Gets cache object as argument, return array of cache tags. |
127
|
|
|
* @param callable $cacheIndexes Gets cache object as argument, return array of cache keys. |
128
|
|
|
* @param callable $listTags Optional, global tags for the list cache. |
129
|
|
|
* @param array $arguments Optional, arguments when parnt method takes arguments that key varies on. |
130
|
|
|
* |
131
|
|
|
* @return object |
132
|
|
|
*/ |
133
|
|
|
final protected function getListCacheValue( |
134
|
|
|
string $key, |
135
|
|
|
callable $backendLoader, |
136
|
|
|
callable $cacheTagger, |
137
|
|
|
callable $cacheIndexes, |
138
|
|
|
callable $listTags = null, |
139
|
|
|
array $arguments = [] |
140
|
|
|
) { |
141
|
|
|
// In-memory |
142
|
|
|
if ($objects = $this->inMemory->get($key)) { |
143
|
|
|
$this->logger->logCacheHit($arguments, 3, true); |
144
|
|
|
|
145
|
|
|
return $objects; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
// Cache pool |
149
|
|
|
$cacheItem = $this->cache->getItem($key); |
150
|
|
View Code Duplication |
if ($cacheItem->isHit()) { |
|
|
|
|
151
|
|
|
$this->logger->logCacheHit($arguments, 3); |
152
|
|
|
$this->inMemory->setMulti($objects = $cacheItem->get(), $cacheIndexes, $key); |
153
|
|
|
|
154
|
|
|
return $objects; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
// Backend |
158
|
|
|
$objects = $backendLoader(); |
159
|
|
|
$this->inMemory->setMulti($objects, $cacheIndexes, $key); |
160
|
|
|
$this->logger->logCacheMiss($arguments, 3); |
161
|
|
|
|
162
|
|
|
if ($listTags !== null) { |
163
|
|
|
$tagSet = [$listTags()]; |
164
|
|
|
} else { |
165
|
|
|
$tagSet = [[]]; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
foreach ($objects as $object) { |
169
|
|
|
$tagSet[] = $cacheTagger($object); |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
$this->cache->save( |
173
|
|
|
$cacheItem |
174
|
|
|
->set($objects) |
175
|
|
|
->tag(array_unique(array_merge(...$tagSet))) |
176
|
|
|
); |
177
|
|
|
|
178
|
|
|
return $objects; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Load several cache items from cache and loggs the hits / misses. |
183
|
|
|
* |
184
|
|
|
* Load items from in-memory cache, symfony cache pool or backend in that order. |
185
|
|
|
* If not cached the returned objects will be placed in cache. |
186
|
|
|
* |
187
|
|
|
* Cache items must be stored with a key in the following format "${keyPrefix}${id}", like "ez-content-info-${id}", |
188
|
|
|
* in order for this method to be able to prefix key on id's and also extract key prefix afterwards. |
189
|
|
|
* |
190
|
|
|
* @param array $ids |
191
|
|
|
* @param string $keyPrefix E.g "ez-content-" |
192
|
|
|
* @param callable $backendLoader Function for loading missing objects, gets array with missing id's as argument, |
193
|
|
|
* expects return value to be array with id as key. Missing items should be missing. |
194
|
|
|
* @param callable $cacheTagger Gets cache object as argument, return array of cache tags. |
195
|
|
|
* @param callable $cacheIndexes Gets cache object as argument, return array of cache keys. |
196
|
|
|
* |
197
|
|
|
* @return array |
198
|
|
|
*/ |
199
|
|
|
final protected function getMultipleCacheValues( |
200
|
|
|
array $ids, |
201
|
|
|
string $keyPrefix, |
202
|
|
|
callable $backendLoader, |
203
|
|
|
callable $cacheTagger, |
204
|
|
|
callable $cacheIndexes, |
205
|
|
|
string $keyPostfix = '' |
206
|
|
|
): array { |
207
|
|
|
if (empty($ids)) { |
208
|
|
|
return []; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
// Generate unique cache keys and check if in-memory |
212
|
|
|
$list = []; |
213
|
|
|
$cacheKeys = []; |
214
|
|
|
$cacheKeysToIdMap = []; |
215
|
|
|
foreach (array_unique($ids) as $id) { |
216
|
|
|
$key = $keyPrefix . $id . $keyPostfix; |
217
|
|
|
if ($object = $this->inMemory->get($key)) { |
218
|
|
|
$list[$id] = $object; |
219
|
|
|
} else { |
220
|
|
|
$cacheKeys[] = $key; |
221
|
|
|
$cacheKeysToIdMap[$key] = $id; |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
// No in-memory misses |
226
|
|
|
if (empty($cacheKeys)) { |
227
|
|
|
$this->logger->logCacheHit($ids, 3, true); |
228
|
|
|
|
229
|
|
|
return $list; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
// Load cache items by cache keys (will contain hits and misses) |
233
|
|
|
$loaded = []; |
234
|
|
|
$cacheMisses = []; |
235
|
|
|
foreach ($this->cache->getItems($cacheKeys) as $key => $cacheItem) { |
236
|
|
|
$id = $cacheKeysToIdMap[$key]; |
237
|
|
|
if ($cacheItem->isHit()) { |
238
|
|
|
$list[$id] = $cacheItem->get(); |
239
|
|
|
$loaded[$id] = $list[$id]; |
240
|
|
|
} else { |
241
|
|
|
$cacheMisses[] = $id; |
242
|
|
|
$list[$id] = $cacheItem; |
243
|
|
|
} |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
// No cache pool misses, cache loaded items in-memory and return |
247
|
|
|
if (empty($cacheMisses)) { |
248
|
|
|
$this->logger->logCacheHit($ids, 3, true); |
249
|
|
|
$this->inMemory->setMulti($loaded, $cacheIndexes); |
250
|
|
|
|
251
|
|
|
return $list; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
// Load missing items, save to cache & apply to list if found |
255
|
|
|
$backendLoadedList = $backendLoader($cacheMisses); |
256
|
|
|
foreach ($cacheMisses as $id) { |
257
|
|
|
if (isset($backendLoadedList[$id])) { |
258
|
|
|
$this->cache->save( |
259
|
|
|
$list[$id] |
260
|
|
|
->set($backendLoadedList[$id]) |
261
|
|
|
->tag($cacheTagger($backendLoadedList[$id])) |
262
|
|
|
); |
263
|
|
|
$loaded[$id] = $backendLoadedList[$id]; |
264
|
|
|
$list[$id] = $backendLoadedList[$id]; |
265
|
|
|
} else { |
266
|
|
|
// not found |
267
|
|
|
unset($list[$id]); |
268
|
|
|
} |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
$this->inMemory->setMulti($loaded, $cacheIndexes); |
272
|
|
|
unset($loaded, $backendLoadedList); |
273
|
|
|
$this->logger->logCacheMiss($cacheMisses, 3); |
274
|
|
|
|
275
|
|
|
return $list; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* Delete cache by keys. |
280
|
|
|
* |
281
|
|
|
* @param array $keys |
282
|
|
|
*/ |
283
|
|
|
final protected function deleteCache(array $keys): void |
284
|
|
|
{ |
285
|
|
|
if (empty($keys)) { |
286
|
|
|
return; |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
$this->inMemory->deleteMulti($keys); |
290
|
|
|
$this->cache->deleteItems($keys); |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* Invalidate cache by tags. |
295
|
|
|
* |
296
|
|
|
* @param array $tags |
297
|
|
|
*/ |
298
|
|
|
final protected function invalidateCache(array $tags): void |
299
|
|
|
{ |
300
|
|
|
if (empty($tags)) { |
301
|
|
|
return; |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
$this->inMemory->clear(); |
305
|
|
|
$this->cache->invalidateTags($tags); |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
/** |
309
|
|
|
* Clear ALL cache. |
310
|
|
|
* |
311
|
|
|
* Only use this in extname situations, or for isolation in tests. |
312
|
|
|
*/ |
313
|
|
|
final protected function clearCache(): void |
314
|
|
|
{ |
315
|
|
|
$this->inMemory->clear(); |
316
|
|
|
$this->cache->clear(); |
317
|
|
|
} |
318
|
|
|
} |
319
|
|
|
|
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.