1 | <?php |
||
2 | |||
3 | namespace Elgg\Cache; |
||
4 | |||
5 | use DateTime; |
||
6 | use Elgg\Config; |
||
7 | use ElggCache; |
||
8 | use Stash\Driver\BlackHole; |
||
9 | use Stash\Driver\Composite; |
||
10 | use Stash\Driver\Ephemeral; |
||
11 | use Stash\Driver\FileSystem; |
||
12 | use Stash\Driver\Memcache; |
||
13 | use Stash\Driver\Redis; |
||
14 | use Stash\Pool; |
||
15 | |||
16 | /** |
||
17 | * Composite cache pool |
||
18 | * |
||
19 | * @internal |
||
20 | */ |
||
21 | class CompositeCache extends ElggCache { |
||
22 | |||
23 | /** |
||
24 | * TTL of saved items (default timeout after a day to prevent anything getting too stale) |
||
25 | */ |
||
26 | protected $ttl = 86400; |
||
27 | |||
28 | /** |
||
29 | * @var Config |
||
30 | */ |
||
31 | protected $config; |
||
32 | |||
33 | /** |
||
34 | * @var int |
||
35 | */ |
||
36 | protected $flags; |
||
37 | |||
38 | /** |
||
39 | * @var Pool |
||
40 | */ |
||
41 | protected $pool; |
||
42 | |||
43 | /** |
||
44 | * @var string |
||
45 | */ |
||
46 | protected $namespace; |
||
47 | |||
48 | /** |
||
49 | * Constructor |
||
50 | * |
||
51 | * @param string $namespace Cache namespace |
||
52 | * @param Config $config Elgg config |
||
53 | * @param int $flags Start flags |
||
54 | * |
||
55 | * @throws \ConfigurationException |
||
56 | */ |
||
57 | 4421 | public function __construct($namespace, Config $config, $flags) { |
|
58 | 4421 | parent::__construct(); |
|
59 | |||
60 | 4421 | $this->namespace = $namespace; |
|
61 | 4421 | $this->config = $config; |
|
62 | 4421 | $this->flags = $flags; |
|
63 | 4421 | $this->pool = $this->createPool(); |
|
64 | 4421 | } |
|
65 | |||
66 | /** |
||
67 | * Returns cache pool |
||
68 | * @return Pool |
||
69 | */ |
||
70 | public function getPool() { |
||
71 | return $this->pool; |
||
72 | } |
||
73 | |||
74 | /** |
||
75 | * Save data in a cache. |
||
76 | * |
||
77 | * @param string $key Name |
||
78 | * @param string $data Value |
||
79 | * @param int|DateTime $expire_after Expire value after |
||
80 | * @param array $invalidation_method Stash invalidation method arguments |
||
81 | * |
||
82 | * @return bool |
||
83 | */ |
||
84 | 5358 | public function save($key, $data, $expire_after = null, $invalidation_method = null) { |
|
85 | 5358 | if (!is_string($key) && !is_int($key)) { |
|
86 | throw new \InvalidArgumentException('key must be string or integer'); |
||
87 | } |
||
88 | |||
89 | 5358 | $item = $this->pool->getItem($this->namespaceKey($key)); |
|
90 | 5358 | $item->lock(); |
|
91 | |||
92 | 5358 | if (is_array($invalidation_method)) { |
|
93 | 13 | call_user_func_array([$item, 'setInvalidationMethod'], $invalidation_method); |
|
94 | } |
||
95 | |||
96 | 5358 | $item->expiresAfter($expire_after ? : $this->ttl); |
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
97 | |||
98 | 5358 | return $item->set($data)->save(); |
|
99 | } |
||
100 | |||
101 | /** |
||
102 | * Load data from the cache using a given key. |
||
103 | * |
||
104 | * @param string $key Name |
||
105 | * |
||
106 | * @return mixed The stored data or false. |
||
107 | */ |
||
108 | 5381 | public function load($key) { |
|
109 | 5381 | if (!is_string($key) && !is_int($key)) { |
|
110 | throw new \InvalidArgumentException('key must be string or integer'); |
||
111 | } |
||
112 | |||
113 | 5381 | $item = $this->pool->getItem($this->namespaceKey($key)); |
|
114 | |||
115 | 5381 | if ($item->isMiss()) { |
|
116 | 5357 | return null; |
|
117 | } |
||
118 | |||
119 | 2377 | return $item->get(); |
|
120 | } |
||
121 | |||
122 | /** |
||
123 | * Invalidate a key |
||
124 | * |
||
125 | * @param string $key Name |
||
126 | * |
||
127 | * @return bool |
||
128 | */ |
||
129 | 1096 | public function delete($key) { |
|
130 | 1096 | if (!is_string($key) && !is_int($key)) { |
|
131 | throw new \InvalidArgumentException('key must be string or integer'); |
||
132 | } |
||
133 | |||
134 | 1096 | $this->pool->deleteItem($this->namespaceKey($key)); |
|
135 | |||
136 | 1096 | return true; |
|
137 | } |
||
138 | |||
139 | /** |
||
140 | * Clear out all the contents of the cache. |
||
141 | * |
||
142 | * @return bool |
||
143 | */ |
||
144 | 5389 | public function clear() { |
|
145 | 5389 | $this->pool->deleteItems([$this->namespaceKey('')]); |
|
146 | |||
147 | 5389 | return true; |
|
148 | } |
||
149 | |||
150 | /** |
||
151 | * Set the namespace of this cache. |
||
152 | * This is useful for cache types (like memcache or static variables) where there is one large |
||
153 | * flat area of memory shared across all instances of the cache. |
||
154 | * |
||
155 | * @param string $namespace Namespace for cache |
||
156 | * |
||
157 | * @return void |
||
158 | */ |
||
159 | public function setNamespace($namespace = "default") { |
||
160 | $this->namespace = $namespace; |
||
161 | } |
||
162 | |||
163 | /** |
||
164 | * Get the namespace currently defined. |
||
165 | * |
||
166 | * @return string |
||
167 | */ |
||
168 | 5389 | public function getNamespace() { |
|
169 | 5389 | return $this->namespace; |
|
170 | } |
||
171 | |||
172 | /** |
||
173 | * Namespace the key |
||
174 | * |
||
175 | * @param string $key Value name |
||
176 | * |
||
177 | * @return string |
||
178 | */ |
||
179 | 5389 | public function namespaceKey($key) { |
|
180 | 5389 | return "/{$this->getNamespace()}/$key"; |
|
181 | } |
||
182 | |||
183 | /** |
||
184 | * Create a new composite stash pool |
||
185 | * @return Pool |
||
186 | * @throws \ConfigurationException |
||
187 | */ |
||
188 | 4421 | protected function createPool() { |
|
189 | 4421 | $drivers = []; |
|
190 | 4421 | $drivers[] = $this->buildRedisDriver(); |
|
191 | 4421 | $drivers[] = $this->buildMemcachedDriver(); |
|
192 | 4421 | $drivers[] = $this->buildFileSystemDriver(); |
|
193 | 4421 | $drivers[] = $this->buildEphemeralDriver(); |
|
194 | 4421 | $drivers[] = $this->buildBlackHoleDriver(); |
|
195 | |||
196 | 4421 | $drivers = array_filter($drivers); |
|
197 | |||
198 | 4421 | if (empty($drivers)) { |
|
199 | throw new \ConfigurationException("Unable to initialize composite cache without drivers"); |
||
200 | } |
||
201 | |||
202 | 4421 | if (count($drivers) > 1) { |
|
203 | 4421 | $driver = new Composite([ |
|
204 | 4421 | 'drivers' => $drivers, |
|
205 | ]); |
||
206 | } else { |
||
207 | 4417 | $driver = array_shift($drivers); |
|
208 | } |
||
209 | |||
210 | 4421 | return new Pool($driver); |
|
211 | } |
||
212 | |||
213 | /** |
||
214 | * Builds Redis driver |
||
215 | * @return null|Redis |
||
216 | */ |
||
217 | 4421 | protected function buildRedisDriver() { |
|
218 | 4421 | if (!($this->flags & ELGG_CACHE_PERSISTENT)) { |
|
219 | 4417 | return null; |
|
220 | } |
||
221 | |||
222 | 4421 | if (!$this->config->redis || !$this->config->redis_servers) { |
|
0 ignored issues
–
show
The expression
$this->config->redis_servers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using
Loading history...
|
|||
223 | 4421 | return null; |
|
224 | } |
||
225 | |||
226 | return new Redis([ |
||
227 | 'servers' => $this->config->redis_servers, |
||
228 | ]); |
||
229 | } |
||
230 | |||
231 | /** |
||
232 | * Builds Memcached driver |
||
233 | * @return null|Memcache |
||
234 | */ |
||
235 | 4421 | protected function buildMemcachedDriver() { |
|
236 | 4421 | if (!($this->flags & ELGG_CACHE_PERSISTENT)) { |
|
237 | 4417 | return null; |
|
238 | } |
||
239 | |||
240 | 4421 | $has_class = class_exists('Memcache') || class_exists('Memcached'); |
|
241 | 4421 | if (!$has_class) { |
|
242 | return null; |
||
243 | } |
||
244 | |||
245 | 4421 | if (!$this->config->memcache || !$this->config->memcache_servers) { |
|
0 ignored issues
–
show
The expression
$this->config->memcache_servers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using
Loading history...
|
|||
246 | 6 | return null; |
|
247 | } |
||
248 | |||
249 | 4421 | return new Memcache([ |
|
250 | 4421 | 'servers' => $this->config->memcache_servers, |
|
251 | 'options' => [ |
||
252 | 4421 | 'prefix_key' => $this->config->memcache_namespace_prefix, |
|
253 | ] |
||
254 | ]); |
||
255 | } |
||
256 | |||
257 | /** |
||
258 | * Builds file system driver |
||
259 | * @return null|FileSystem |
||
260 | */ |
||
261 | 4421 | protected function buildFileSystemDriver() { |
|
262 | 4421 | if (!($this->flags & ELGG_CACHE_FILESYSTEM)) { |
|
263 | 4420 | return null; |
|
264 | } |
||
265 | |||
266 | 4418 | $path = $this->config->cacheroot ? : $this->config->dataroot; |
|
267 | 4418 | if (!$path) { |
|
268 | 1 | return null; |
|
269 | } |
||
270 | |||
271 | 4418 | return new FileSystem([ |
|
272 | 4418 | 'path' => $path, |
|
273 | ]); |
||
274 | } |
||
275 | |||
276 | /** |
||
277 | * Builds in-memory driver |
||
278 | * @return Ephemeral |
||
279 | */ |
||
280 | 4421 | protected function buildEphemeralDriver() { |
|
281 | 4421 | if (!($this->flags & ELGG_CACHE_RUNTIME)) { |
|
282 | 20 | return null; |
|
283 | } |
||
284 | |||
285 | 4421 | return new Ephemeral(); |
|
286 | } |
||
287 | |||
288 | /** |
||
289 | * Builds null cache driver |
||
290 | * @return BlackHole |
||
291 | */ |
||
292 | 4421 | protected function buildBlackHoleDriver() { |
|
293 | 4421 | if (!($this->flags & ELGG_CACHE_BLACK_HOLE)) { |
|
294 | 4421 | return null; |
|
295 | } |
||
296 | |||
297 | return new BlackHole(); |
||
298 | } |
||
299 | } |
||
300 |