1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* File containing the CacheServiceDecorator class. |
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\Adapter\TransactionAwareAdapterInterface; |
12
|
|
|
use eZ\Publish\Core\Persistence\Cache\Adapter\Item\TransactionItem; |
13
|
|
|
use Stash\Interfaces\PoolInterface; |
14
|
|
|
use Tedivm\StashBundle\Service\CacheItem; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* Class CacheServiceDecorator. |
18
|
|
|
* |
19
|
|
|
* Wraps the Cache Service for Spi cache to apply key prefix for the cache |
20
|
|
|
*/ |
21
|
|
|
class CacheServiceDecorator implements TransactionAwareAdapterInterface |
22
|
|
|
{ |
23
|
|
|
const SPI_CACHE_KEY_PREFIX = 'ez_spi'; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* @var \Stash\Interfaces\PoolInterface |
27
|
|
|
*/ |
28
|
|
|
protected $cachePool; |
29
|
|
|
|
30
|
|
|
/** @var int */ |
31
|
|
|
protected $transactionDepth = 0; |
32
|
|
|
|
33
|
|
|
/** @var array */ |
34
|
|
|
protected $deferredClearKeys = []; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Constructs the cache service decorator. |
38
|
|
|
* |
39
|
|
|
* @param \Stash\Interfaces\PoolInterface $cachePool |
40
|
|
|
*/ |
41
|
|
|
public function __construct(PoolInterface $cachePool) |
42
|
|
|
{ |
43
|
|
|
$this->cachePool = $cachePool; |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Prepend key with prefix and support array format Stash supported before. |
48
|
|
|
* |
49
|
|
|
* {@see \Psr\Cache\CacheItemPoolInterface} |
50
|
|
|
* |
51
|
|
|
* @internal param array|string $key , $key, $key... |
52
|
|
|
* |
53
|
|
|
* @return \Stash\Interfaces\ItemInterface |
54
|
|
|
*/ |
55
|
|
|
public function getItem() |
56
|
|
|
{ |
57
|
|
|
$args = func_get_args(); |
58
|
|
|
|
59
|
|
|
if (empty($args)) { |
60
|
|
|
return $this->handleTransactionItems( |
61
|
|
|
[$this->cachePool->getItem(self::SPI_CACHE_KEY_PREFIX)] |
62
|
|
|
)[0]; |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
// Upstream seems to no longer support array, so we flatten it |
66
|
|
|
if (!isset($args[1]) && is_array($args[0])) { |
67
|
|
|
$key = implode('/', array_map([$this, 'washKey'], $args[0])); |
68
|
|
View Code Duplication |
} else { |
|
|
|
|
69
|
|
|
$key = '' . implode('/', array_map([$this, 'washKey'], $args)); |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
$key = $key === '' ? self::SPI_CACHE_KEY_PREFIX : self::SPI_CACHE_KEY_PREFIX . '/' . $key; |
73
|
|
|
|
74
|
|
|
return $this->handleTransactionItems( |
75
|
|
|
[$this->cachePool->getItem($key)] |
76
|
|
|
)[0]; |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Prepend keys with prefix. |
81
|
|
|
* |
82
|
|
|
* {@see \Psr\Cache\CacheItemPoolInterface} |
83
|
|
|
* |
84
|
|
|
* @param array $keys |
85
|
|
|
* @return \Stash\Interfaces\ItemInterface[] |
86
|
|
|
*/ |
87
|
|
|
public function getItems(array $keys = []) |
88
|
|
|
{ |
89
|
|
|
$prefix = self::SPI_CACHE_KEY_PREFIX; |
90
|
|
|
$keys = array_map( |
91
|
|
|
function ($key) use ($prefix) { |
92
|
|
|
$key = $this->washKey($key); |
93
|
|
|
|
94
|
|
|
return $key === '' ? $prefix : $prefix . '/' . $key; |
95
|
|
|
}, |
96
|
|
|
$keys |
97
|
|
|
); |
98
|
|
|
|
99
|
|
|
return $this->handleTransactionItems( |
100
|
|
|
$this->cachePool->getItems($keys) |
101
|
|
|
); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* @param \Stash\Interfaces\ItemInterface[] $items |
106
|
|
|
* @return \Stash\Interfaces\ItemInterface[] |
107
|
|
|
*/ |
108
|
|
|
private function handleTransactionItems(array $items) |
109
|
|
|
{ |
110
|
|
|
if ($this->transactionDepth === 0) { |
111
|
|
|
return $items; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
// If in transaction we set callback for save()/clear(), & for isMiss() detect if key is a deferred miss |
115
|
|
|
foreach ($items as $item) { |
116
|
|
|
/* @var TransactionItem $item */ |
117
|
|
|
$item->setClearCallback(function ($key) { |
118
|
|
|
$this->clearOrDefer([$key]); |
119
|
|
|
}); |
120
|
|
|
$item->setIsClearedCallback(function ($key) { |
121
|
|
|
// Due to keys in Stash being hierarchical we need to check if key or prefix of key has been cleared |
122
|
|
|
foreach ($this->deferredClearKeys as $clearedKey) { |
123
|
|
|
if ($key === $clearedKey || stripos($key, $clearedKey) === 0) { |
124
|
|
|
return true; |
125
|
|
|
} |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
return false; |
129
|
|
|
}); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
return $items; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Remove slashes from start and end of keys, and for content replace it with _ to avoid issues for Stash. |
137
|
|
|
* |
138
|
|
|
* @param string $key |
139
|
|
|
* @return string |
140
|
|
|
*/ |
141
|
|
|
private function washKey($key) |
142
|
|
|
{ |
143
|
|
|
return str_replace('/', '_', trim($key, '/')); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Clears the cache for the key, or if none is specified clears the entire cache. The key can be either |
148
|
|
|
* a series of string arguments, or an array. |
149
|
|
|
* |
150
|
|
|
* @internal |
151
|
|
|
* @param array|null|string $key , $key, $key... |
152
|
|
|
* @return bool |
153
|
|
|
*/ |
154
|
|
|
public function clear(...$key) |
155
|
|
|
{ |
156
|
|
|
// Make washed string key out of the arguments |
157
|
|
|
if (empty($key)) { |
158
|
|
|
$key = self::SPI_CACHE_KEY_PREFIX; |
159
|
|
|
} elseif (!isset($key[1]) && is_array($key[0])) { |
160
|
|
|
$key = self::SPI_CACHE_KEY_PREFIX . '/' . implode('/', array_map([$this, 'washKey'], $key[0])); |
161
|
|
View Code Duplication |
} else { |
|
|
|
|
162
|
|
|
$key = self::SPI_CACHE_KEY_PREFIX . '/' . implode('/', array_map([$this, 'washKey'], $key)); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
return $this->clearOrDefer([$key]); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
private function clearOrDefer(array $keys) |
169
|
|
|
{ |
170
|
|
|
// Store for later if in transaction |
171
|
|
|
if ($this->transactionDepth > 0) { |
172
|
|
|
$this->deferredClearKeys = array_merge($this->deferredClearKeys, $keys); |
173
|
|
|
|
174
|
|
|
return true; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
return $this->executeClear($keys); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
private function executeClear(array $keys) |
181
|
|
|
{ |
182
|
|
|
// Detect full cache clear, if so ignore everything else |
183
|
|
|
if (in_array(self::SPI_CACHE_KEY_PREFIX, $keys, true)) { |
184
|
|
|
$item = $this->cachePool->getItem(self::SPI_CACHE_KEY_PREFIX); |
185
|
|
|
|
186
|
|
|
return $item->clear(); |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
return $this->cachePool->deleteItems($keys); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* {@inheritdoc}. |
194
|
|
|
*/ |
195
|
|
|
public function beginTransaction() |
196
|
|
|
{ |
197
|
|
|
if ($this->transactionDepth === 0) { |
198
|
|
|
// Wrap Item(s) in order to also handle calls to $item->save() and $item->clear() |
199
|
|
|
$this->cachePool->setItemClass(TransactionItem::class); |
200
|
|
|
} |
201
|
|
|
++$this->transactionDepth; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* {@inheritdoc}. |
206
|
|
|
*/ |
207
|
|
|
public function commitTransaction() |
208
|
|
|
{ |
209
|
|
|
if ($this->transactionDepth === 0) { |
210
|
|
|
// ignore, might have been a rollback |
211
|
|
|
return; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
--$this->transactionDepth; |
215
|
|
|
|
216
|
|
|
// Cache commit time, it's now time to share the changes with the pool |
217
|
|
|
if ($this->transactionDepth === 0) { |
218
|
|
|
$this->cachePool->setItemClass(CacheItem::class); |
219
|
|
|
if (!empty($this->deferredClearKeys)) { |
220
|
|
|
$this->executeClear($this->deferredClearKeys); |
221
|
|
|
$this->deferredClearKeys = []; |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* {@inheritdoc}. |
228
|
|
|
*/ |
229
|
|
|
public function rollbackTransaction() |
230
|
|
|
{ |
231
|
|
|
// A rollback in SQL will by default set transaction level to 0 & wipe transaction changes, so we do the same. |
232
|
|
|
$this->cachePool->setItemClass(CacheItem::class); |
233
|
|
|
$this->transactionDepth = 0; |
234
|
|
|
$this->deferredClearKeys = []; |
235
|
|
|
} |
236
|
|
|
} |
237
|
|
|
|
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.