Passed
Push — 3.x ( 589a0f...691809 )
by Jerome
65:54 queued 11s
created

CompositeCache::load()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 31
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 10.1359

Importance

Changes 0
Metric Value
cc 7
eloc 15
nc 10
nop 2
dl 0
loc 31
ccs 9
cts 15
cp 0.6
crap 10.1359
rs 8.8333
c 0
b 0
f 0
1
<?php
2
3
namespace Elgg\Cache;
4
5
use DateTime;
6
use Elgg\Config;
7
use ElggCache;
8
use Stash\Driver\Apc;
9
use Stash\Driver\BlackHole;
10
use Stash\Driver\Composite;
11
use Stash\Driver\Ephemeral;
12
use Stash\Driver\FileSystem;
13
use Stash\Driver\Memcache;
14
use Stash\Driver\Redis;
15
use Stash\Pool;
16
17
/**
18
 * Composite cache pool
19
 *
20
 * @internal
21
 */
22
class CompositeCache extends ElggCache {
23
24
	/**
25
	 * TTL of saved items (default timeout after a day to prevent anything getting too stale)
26
	 */
27
	protected $ttl = 86400;
28
29
	/**
30
	 * @var Config
31
	 */
32
	protected $config;
33
34
	/**
35
	 * @var int
36
	 */
37
	protected $flags;
38
39
	/**
40
	 * @var Pool
41
	 */
42
	protected $pool;
43
44
	/**
45
	 * @var string
46
	 */
47
	protected $namespace;
48
49
	/**
50
	 * Constructor
51
	 *
52
	 * @param string $namespace Cache namespace
53
	 * @param Config $config    Elgg config
54
	 * @param int    $flags     Start flags
55
	 *
56
	 * @throws \ConfigurationException
57
	 */
58 4889
	public function __construct($namespace, Config $config, $flags) {
59 4889
		parent::__construct();
60
61 4889
		$this->namespace = $namespace;
62 4889
		$this->config = $config;
63 4889
		$this->flags = $flags;
64 4889
		$this->pool = $this->createPool();
65 4889
	}
66
67
	/**
68
	 * Returns cache pool
69
	 * @return Pool
70
	 */
71
	public function getPool() {
72
		return $this->pool;
73
	}
74
75
	/**
76
	 * Save data in a cache.
77
	 *
78
	 * @param string       $key  Name
79
	 * @param string       $data Value
80
	 * @param int|DateTime $ttl  Expire value after
81
	 *
82
	 * @return bool
83
	 */
84 6023
	public function save($key, $data, $ttl = null) {
85 6023
		if ($this->disabled) {
86 4
			return false;
87
		}
88
89 6023
		if (!is_string($key) && !is_int($key)) {
90
			throw new \InvalidArgumentException('key must be string or integer');
91
		}
92
93 6023
		$item = $this->pool->getItem($this->namespaceKey($key));
94 6023
		$item->lock();
95
96 6023
		$item->setTTL($ttl ? : $this->ttl);
97
98 6023
		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
	 * @param array  $invalidation_method Stash invalidation method arguments
106
	 *
107
	 * @return mixed The stored data or false.
108
	 */
109 6045
	public function load($key, $invalidation_method = null) {
110 6045
		if ($this->disabled) {
111 4
			return null;
112
		}
113
114 6045
		if (!is_string($key) && !is_int($key)) {
115
			throw new \InvalidArgumentException('key must be string or integer');
116
		}
117
118 6045
		$item = $this->pool->getItem($this->namespaceKey($key));
119
	
120 6045
		if (is_array($invalidation_method)) {
121
			call_user_func_array([$item, 'setInvalidationMethod'], $invalidation_method);
122
		}
123
		
124
		try {
125 6045
			if ($item->isMiss()) {
126 6021
				return null;
127
			}
128
			
129 6043
			return $item->get();
130
		} catch (\Error $e) {
131
			// catching parsing errors in file driver, because of potential race conditions during write
132
			// this will cause corrupted data in the file and will crash the site when reading the file
133
			elgg_log(__METHOD__ . " failed for key: {$this->getNamespace()}/{$key} with error: {$e->getMessage()}", 'ERROR');
134
			
135
			// remove the item from the cache so it can try to generate this item again
136
			$this->delete($key);
137
		}
138
139
		return null;
140
	}
141
142
	/**
143
	 * Invalidate a key
144
	 *
145
	 * @param string $key Name
146
	 *
147
	 * @return bool
148
	 */
149 1382
	public function delete($key) {
150 1382
		if ($this->disabled) {
151 18
			return false;
152
		}
153
154 1379
		if (!is_string($key) && !is_int($key)) {
155 348
			throw new \InvalidArgumentException('key must be string or integer');
156
		}
157
158 1379
		$this->pool->deleteItem($this->namespaceKey($key));
159
160 1379
		return true;
161
	}
162
163
	/**
164
	 * Clear out all the contents of the cache.
165
	 *
166
	 * @return bool
167
	 */
168 6053
	public function clear() {
169 6053
		$this->pool->deleteItems([$this->namespaceKey('')]);
170
171 6053
		return true;
172
	}
173
174
	/**
175
	 * Set the namespace of this cache.
176
	 * This is useful for cache types (like memcache or static variables) where there is one large
177
	 * flat area of memory shared across all instances of the cache.
178
	 *
179
	 * @param string $namespace Namespace for cache
180
	 *
181
	 * @return void
182
	 */
183
	public function setNamespace($namespace = "default") {
184
		$this->namespace = $namespace;
185
	}
186
187
	/**
188
	 * Get the namespace currently defined.
189
	 *
190
	 * @return string
191
	 */
192 6053
	public function getNamespace() {
193 6053
		return $this->namespace;
194
	}
195
196
	/**
197
	 * Namespace the key
198
	 *
199
	 * @param string $key Value name
200
	 *
201
	 * @return string
202
	 */
203 6053
	public function namespaceKey($key) {
204 6053
		return "/{$this->getNamespace()}/$key";
205
	}
206
207
	/**
208
	 * Create a new composite stash pool
209
	 * @return Pool
210
	 * @throws \ConfigurationException
211
	 */
212 4889
	protected function createPool() {
213 4889
		$drivers = [];
214 4889
		$drivers[] = $this->buildEphemeralDriver();
215 4889
		$drivers[] = $this->buildApcDriver();
216 4889
		$drivers[] = $this->buildRedisDriver();
217 4889
		$drivers[] = $this->buildMemcachedDriver();
218 4889
		$drivers[] = $this->buildFileSystemDriver();
219 4889
		$drivers[] = $this->buildBlackHoleDriver();
220 4889
		$drivers = array_filter($drivers);
221
222 4889
		if (empty($drivers)) {
223
			throw new \ConfigurationException("Unable to initialize composite cache without drivers");
224
		}
225
226 4889
		if (count($drivers) > 1) {
227 4888
			$driver = new Composite([
228 4888
				'drivers' => $drivers,
229
			]);
230
		} else {
231 4889
			$driver = array_shift($drivers);
232
		}
233
234 4889
		return new Pool($driver);
235
	}
236
237
	/**
238
	 * Builds APC driver
239
	 * @return null|Apc
240
	 */
241 4889
	protected function buildApcDriver() {
242 4889
		if (!($this->flags & ELGG_CACHE_APC)) {
243 4889
			return null;
244
		}
245
246 4888
		if (!extension_loaded('apc') || !ini_get('apc.enabled')) {
247 4888
			return null;
248
		}
249
250
		return new Apc();
251
	}
252
253
	/**
254
	 * Builds Redis driver
255
	 * @return null|Redis
256
	 */
257 4889
	protected function buildRedisDriver() {
258 4889
		if (!($this->flags & ELGG_CACHE_PERSISTENT)) {
259 4888
			return null;
260
		}
261
262 4889
		if (!$this->config->redis || empty($this->config->redis_servers)) {
263 4889
			return null;
264
		}
265
266
		return new Redis([
267
			'servers' => $this->config->redis_servers,
268
		]);
269
	}
270
271
	/**
272
	 * Builds Memcached driver
273
	 * @return null|Memcache
274
	 */
275 4889
	protected function buildMemcachedDriver() {
276 4889
		if (!($this->flags & ELGG_CACHE_PERSISTENT)) {
277 4888
			return null;
278
		}
279
280 4889
		if (!$this->config->memcache || empty($this->config->memcache_servers)) {
281 4889
			return null;
282
		}
283
			
284
		$has_class = class_exists('Memcache') || class_exists('Memcached');
285
		if (!$has_class) {
286
			return null;
287
		}
288
289
		return new Memcache([
290
			'servers' => $this->config->memcache_servers,
291
			'options' => [
292
				'prefix_key' => $this->config->memcache_namespace_prefix,
293
			]
294
		]);
295
	}
296
297
	/**
298
	 * Builds file system driver
299
	 * @return null|FileSystem
300
	 */
301 4889
	protected function buildFileSystemDriver() {
302 4889
		if (!($this->flags & ELGG_CACHE_FILESYSTEM)) {
303 4889
			return null;
304
		}
305
306 4888
		$path = $this->config->cacheroot ? : $this->config->dataroot;
307 4888
		if (!$path) {
308
			return null;
309
		}
310
311 4888
		return new FileSystem([
312 4888
			'path' => $path,
313
		]);
314
	}
315
316
	/**
317
	 * Builds in-memory driver
318
	 * @return null|Ephemeral
319
	 */
320 4889
	protected function buildEphemeralDriver() {
321 4889
		if (!($this->flags & ELGG_CACHE_RUNTIME)) {
322 10
			return null;
323
		}
324
325 4889
		return new Ephemeral();
326
	}
327
328
	/**
329
	 * Builds null cache driver
330
	 * @return null|BlackHole
331
	 */
332 4889
	protected function buildBlackHoleDriver() {
333 4889
		if (!($this->flags & ELGG_CACHE_BLACK_HOLE)) {
334 4889
			return null;
335
		}
336
337
		return new BlackHole();
338
	}
339
}
340