CacheMemcached::__construct()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
dl 0
loc 17
rs 9.2
c 2
b 0
f 0
cc 4
eloc 12
nc 4
nop 0
1
<?php
2
3
namespace Cachearium\Backend;
4
5
use Cachearium\Backend\CacheRAM;
6
use Cachearium\CacheLogEnum;
7
use Cachearium\CacheKey;
8
use Cachearium\Exceptions\NotCachedException;
9
10
/**
11
 * Cache class which uses memcache.
12
 *
13
 * Keys are set up as: [namespace]_[id]_[subgroup]
14
 *
15
 * We inherit from CacheRAM to avoid fetching things multiple times.
16
 *
17
 */
18
class CacheMemcached extends CacheRAM {
19
	/**
20
	 * The memcache var
21
	 * @var \Memcached
22
	 */
23
	private $memcached;
24
25
	private $fetches = 0; /// number of times we had to hit memcache directly
26
27
	/**
28
	 * Cache constructor (this is a singleton).
29
	 *
30
	 * @param $serves optional. If present, addServers() is called with this parameter
31
	 * but only if the singleton is being created for the first time.
32
	 * @return Cache The cache singleton.
33
	 * @codeCoverageIgnore
34
	 */
35
	static public function singleton($servers = []) {
36
		static $instances;
37
		if (!isset($instances)) {
38
			$instances = new CacheMemcached();
39
			if (count($servers)) {
40
				$instances->addServers($servers);
41
			}
42
		}
43
44
		return $instances;
45
	}
46
47
	/**
48
	 * Is memcached available in this system?
49
	 *
50
	 * @return boolean
51
	 */
52
	static public function hasMemcachedExt() {
53
		return extension_loaded('memcached');
54
	}
55
56
	// @codeCoverageIgnoreStart
57
	// Prevent users to clone the instance
58
	public function __clone() {
59
		trigger_error('Cloning is not allowed.', LH_TRIGGER_UNEXPECTED);
60
	}
61
	// @codeCoverageIgnoreEnd
62
63
	/**
64
	 * @codeCoverageIgnore
65
	 */
66
	public function errorCallback() {
67
		// memcache error, probably offline. Logging to DB is bad (will overflow
68
		// the DB). We should really restart memcached
69
		// TODO: via Batch?
70
		$this->disable();
71
	}
72
73
	public function getFetches() {
74
		return []; // TODO
75
		foreach ($data as $item) {
76
			$x = unserialize($item['keys']);
77
			if ($x === false) {
78
				continue;
79
			}
80
81
			parent::store($x, $item);
82
		}
83
84
		return $this->fetches;
85
	}
86
87
	/**
88
	 * Constructor.
89
	 * @codeCoverageIgnore
90
	 */
91
	private function __construct() {
92
		if (!self::hasMemcachedExt()) {
93
			$this->disable();
94
			return;
95
		}
96
		$this->memcached = new \Memcached;
97
		if (!$this->memcached) {
98
			$this->disable();
99
			return;
100
		}
101
102
		if (\Memcached::HAVE_IGBINARY) {
103
			$this->memcached->setOption(\Memcached::OPT_SERIALIZER, \Memcached::SERIALIZER_IGBINARY);
104
		}
105
		$this->memcached->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
106
		$this->lifetime = 3600;
107
	}
108
109
	/**
110
	 *
111
	 * @param array $servers Each entry in servers is supposed to be an array
112
	 *  containing hostname, port, and, optionally, weight of the server.
113
	 * $servers = array(
114
	 * array('mem1.domain.com', 11211, 33),
115
	 * array('mem2.domain.com', 11211, 67)
116
	 * );
117
	 * @return boolean
118
	 * @codeCoverageIgnore
119
	 */
120
	public function addServers($servers) {
121
		if (!self::hasMemcachedExt()) {
122
			return false;
123
		}
124
		return $this->memcached->addServers($servers);
125
	}
126
127
	/**
128
	 * Converts cachekey to a string for the data group.
129
	 *
130
	 * @param CacheKey $k
131
	 * @return string
132
	 */
133
	private function getGroupString(CacheKey $k) {
134
		return md5(strtr($this->namespace . $k->getBase() . $k->getId(), ' ', '_'));
135
	}
136
137
	/**
138
	 * (non-PHPdoc)
139
	 * @see \Cachearium\Backend\CacheRAM::hashKey()
140
	 */
141
	protected function hashKey(CacheKey $k) {
142
		$group = $this->getGroupString($k);
143
		$ns_key = $this->memcached->get($group);
144
145
		// if not set, initialize it
146
		if ($ns_key == false) {
147
			$ns_key = 1;
148
			$this->memcached->set($group, $ns_key);
149
		}
150
		$group = $group . $ns_key;
151
152
		if (!is_string($k->sub)) {
153
			$sub = md5(serialize($k->sub));
154
		}
155
		else {
156
			$sub = $k->sub;
157
		}
158
		$group .= $sub;
159
160
		return $group;
161
	}
162
163
	/**
164
	 * (non-PHPdoc)
165
	 * @see \Cachearium\Backend\CacheRAM::get()
166
	 */
167
	public function get(CacheKey $k) {
168
		// @codeCoverageIgnoreStart
169
		if (!$this->enabled) {
170
			throw new NotCachedException();
171
		}
172
		// @codeCoverageIgnoreEnd
173
174
		// see if it is in RAM
175
		$should_log = $this->should_log;
176
		try {
177
			$this->should_log = false;
178
			$data = parent::get($k);
179
			$this->should_log = $should_log;
180
			$this->log(CacheLogEnum::PREFETCHED, $k);
181
			return $data;
182
		}
183
		catch (NotCachedException $e) {
184
			$this->should_log = $should_log;
185
		}
186
187
		$group = $this->hashKey($k);
188
189
		$this->fetches++;
190
		$retval = $this->memcached->get($group);
191
192
		$this->log(
193
			($retval !== false ? CacheLogEnum::ACCESSED : CacheLogEnum::MISSED),
194
			$k
195
		);
196
		if ($retval == false) {
197
			throw new NotCachedException();
198
		}
199
200
		$x = unserialize($retval);
201
		if ($x === false) {
202
			throw new NotCachedException();
203
		}
204
205
		parent::store($x, $k);
206
207
		return $x;
208
	}
209
210
	/**
211
	 * (non-PHPdoc)
212
	 * @see \Cachearium\Backend\CacheRAM::increment()
213
	 */
214
	public function increment($value, CacheKey $k, $default = 0) {
215
		// @codeCoverageIgnoreStart
216
		if (!$this->enabled) {
217
			return $default;
218
		}
219
		// @codeCoverageIgnoreEnd
220
221
		$group = $this->hashKey($k);
222
223
		$this->log(CacheLogEnum::SAVED, $k);
224
225
		$x = $this->memcached->increment(
226
			$group, $value, $default, $this->lifetime
227
		);
228
		parent::store($x, $k, $this->lifetime);
229
230
		return $x;
231
	}
232
233
	/**
234
	 * (non-PHPdoc)
235
	 * @see \Cachearium\Backend\CacheRAM::store()
236
	 */
237
	public function store($data, CacheKey $k, $lifetime = 0) {
238
		// @codeCoverageIgnoreStart
239
		if (!$this->enabled) {
240
			return false;
241
		}
242
		// @codeCoverageIgnoreEnd
243
244
		$group = $this->hashKey($k);
245
246
		$this->log(CacheLogEnum::SAVED, $k);
247
248
		$x = $this->memcached->set(
249
			$group, serialize($data), $lifetime ? $lifetime : $this->lifetime
250
		);
251
		parent::store($data, $k, $lifetime);
252
253
		return $x;
254
	}
255
256
	/**
257
	 * (non-PHPdoc)
258
	 * @see \Cachearium\Backend\CacheRAM::delete()
259
	 */
260
	public function delete(CacheKey $k) {
261
		// @codeCoverageIgnoreStart
262
		if (!$this->enabled) {
263
			throw new NotCachedException();
264
		}
265
		// @codeCoverageIgnoreEnd
266
267
		$group = $this->hashKey($k);
268
269
		$this->log(CacheLogEnum::DELETED, $k);
270
271
		parent::delete($k);
272
		return $this->memcached->delete($group);
273
	}
274
275
	/**
276
	 * (non-PHPdoc)
277
	 * @see \Cachearium\Backend\CacheRAM::cleanP()
278
	 */
279
	public function cleanP($base, $id) {
280
		// @codeCoverageIgnoreStart
281
		if (!$this->enabled) {
282
			throw new NotCachedException();
283
		}
284
		// @codeCoverageIgnoreEnd
285
286
		$group = $this->getGroupString(new CacheKey($base, $id));
287
288
		parent::cleanP($base, $id);
289
		$this->memcached->increment($group);
290
		return true;
291
	}
292
293
	/**
294
	 * (non-PHPdoc)
295
	 * @see \Cachearium\Backend\CacheRAM::clear()
296
	 */
297
	public function clear() {
298
		if ($this->memcached) {
299
			$this->memcached->flush();
300
		}
301
		parent::clear();
302
		return true;
303
	}
304
305
	public function prefetchKeys($keys) {
306
		$retval = $this->memcached->get($keys);
307
		foreach ($retval as $i) {
308
		}
309
	}
310
311
	/**
312
	 * (non-PHPdoc)
313
	 * @see \Cachearium\Backend\CacheRAM::prefetch()
314
	 */
315
	public function prefetch($data) {
316
		$keys = array();
317
318
		foreach ($data as &$item) {
319
			$keys[] = $this->hashKey($item);
320
		}
321
322
		$this->memcached->getDelayed($keys);
323
324
		// TODO: fetchall vs get?
325
	}
326
327
	/**
328
	 * Clear prefetched data. This is rarely useful.
329
	 */
330
	public function prefetchClear() {
331
		parent::clear();
332
	}
333
334
	/**
335
	 * (non-PHPdoc)
336
	 * @see \Cachearium\Backend\CacheRAM::report()
337
	 * @codeCoverageIgnore
338
	 */
339
	public function report() {
340
		if ($this->should_log == false) {
341
			return;
342
		}
343
		echo '<style>
344
			.cache-success { background-color: #468847; border-radius: 3px; color: #FFF; padding: 2px 4px; }
345
			.cache-prefetched { background-color: #76F877; border-radius: 3px; color: #FFF; padding: 2px 4px; }
346
			.cache-miss { background-color: #B94A48; border-radius: 3px; color: #FFF; padding: 2px 4px; }
347
			.cache-save { background-color: #0694F8; border-radius: 3px; color: #FFF; padding: 2px 4px; }
348
			.cache-deleted { background-color: #F89406; border-radius: 3px; color: #FFF; padding: 2px 4px; }
349
			.cache-cleaned { background-color: #F894F8; border-radius: 3px; color: #FFF; padding: 2px 4px; }
350
		</style>';
351
		echo '<div class="cachearium cachearium-memcache"><h2>Cache MemCache system</h2>';
352
		echo '<h3>System is: ' . ($this->enabled ? 'enabled' : 'disabled') . '</h3>';
353
		echo '<h3>Total fetches: ' . ($this->fetches) . '</h3>';
354
355
		$stats = array_fill_keys(array_keys(CacheLogEnum::getNames()), 0);
356
		echo '<ul>';
357
		foreach ($this->cache_log as $entry) {
358
			echo '<li>' . CacheLogEnum::getName($entry['status']) . $entry['message'] . '</li>';
359
			$stats[$entry['status']]++;
360
		}
361
		echo '</ul>';
362
363
		echo '<ul>';
364
		foreach ($stats as $key => $val) {
365
			echo '<li>' . CacheLogEnum::getName($key) . '=' . $val . '</li>';
366
		}
367
		echo '</ul>';
368
		echo '</div>';
369
	}
370
}
371