Passed
Push — master ( c0a3a7...3b84a4 )
by Jeroen
58:51
created

engine/classes/Elgg/Cache/CompositeCache.php (3 issues)

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
It seems like $expire_after ?: $this->ttl can also be of type DateTime; however, parameter $time of Stash\Interfaces\ItemInterface::expiresAfter() does only seem to accept integer|DateInterval, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

96
		$item->expiresAfter(/** @scrutinizer ignore-type */ $expire_after ? : $this->ttl);
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
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

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
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

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