1 <?php
2
3 namespace Alo\Cache;
4
5 use Memcache;
6 use Memcached;
7
8 if(!defined('GEN_START')) {
9 http_response_code(404);
10 die();
11 }
12
13 \Alo::loadConfig('memcached');
14
15 /**
16 * A wrapper for PHP's Memcached extension. Will try to use the Memcached class
17 * first, if it doesn't exist, will use Memcache.
18 *
19 * @author Art <a.molcanovas@gmail.com>
20 * @package Cache
21 */
22 class MemcachedWrapper extends AbstractCache {
23
24 /**
25 * Defines the class as Memcached
26 *
27 * @var int
28 */
29 const CLASS_MEMCACHED = 1;
30
31 /**
32 * Defines the class as Memcache
33 *
34 * @var int
35 */
36 const CLASS_MEMCACHE = 2;
37 /**
38 * Whether the relevant cache extension is loaded
39 *
40 * @var boolean
41 */
42 protected static $loaded = null;
43 /**
44 * The memcached instance
45 *
46 * @var Memcache|Memcached
47 */
48 protected $client;
49
50 /**
51 * Instantiates the class
52 *
53 * @author Art <a.molcanovas@gmail.com>
54 *
55 * @param boolean $initialise_default_server Whether to add a server on construct
56 */
57 function __construct($initialise_default_server = true) {
58 if(self::$loaded === null) {
59 if(class_exists('\Memcached', false)) {
60 self::$loaded = self::CLASS_MEMCACHED;
61 } elseif(class_exists('\Memcache')) {
62 self::$loaded = self::CLASS_MEMCACHE;
63 } else {
64 self::$loaded = false;
65 }
66 }
67
68 if(self::$loaded !== null) {
69 $this->client = self::$loaded === self::CLASS_MEMCACHED ? new Memcached() : new Memcache();
70 if($initialise_default_server) {
71 $this->addServer();
72 }
73 } else {
74 trigger_error('Memcached extension not loaded - caching '
75 . 'functions will not work',
76 E_USER_NOTICE);
77 }
78 parent::__construct();
79
80 \Log::debug(self::$loaded ? 'Loaded MemcachedWrapper' : 'MemcachedWrapper not loaded: extension unavailable');
81 }
82
83 /**
84 * Adds a server to the pool
85 *
86 * @author Art <a.molcanovas@gmail.com>
87 *
88 * @param string $ip The server IP
89 * @param int $port The server port
90 * @param int $weight The server's weight, ie how likely it is to be used
91 *
92 * @return boolean
93 */
94 function addServer($ip = ALO_MEMCACHED_IP, $port = ALO_MEMCACHED_PORT, $weight = 1) {
95 \Log::debug('Added MemcachedWrapper server ' . $ip . ':' . $port
96 . ' with a weight of ' . $weight);
97
98 if(self::$loaded === self::CLASS_MEMCACHED) {
99 return $this->client->addServer($ip, $port, $weight);
100 } elseif(self::$loaded === self::CLASS_MEMCACHE) {
101 return $this->client->addserver($ip, $port, null, $weight);
102 } else {
103 return false;
104 }
105 }
106
107 /**
108 * Deletes a memcache key
109 *
110 * @author Art <a.molcanovas@gmail.com>
111 *
112 * @param string $key The key
113 *
114 * @return boolean
115 */
116 function delete($key) {
117 return self::$loaded ? $this->client->delete($key) : false;
118 }
119
120 /**
121 * Instantiates the class
122 *
123 * @author Art <a.molcanovas@gmail.com>
124 *
125 * @param boolean $initialise_default_server Whether to add a server on construct
126 *
127 * @return MemcachedWrapper
128 */
129 static function MemcachedWrapper($initialise_default_server = true) {
130 return new MemcachedWrapper($initialise_default_server);
131 }
132
133 /**
134 * Returns the loaded cache class
135 *
136 * @author Art <a.molcanovas@gmail.com>
137 * @return string|null
138 */
139 function getLoadedClass() {
140 return self::$loaded ? get_class($this->client) : null;
141 }
142
143 /**
144 * Gets cache process info
145 *
146 * @author Art <a.molcanovas@gmail.com>
147 * @return array
148 */
149 function getStats() {
150 return self::$loaded ? $this->client->getStats() : false;
151 }
152
153 /**
154 * Checks if a Memcache or Memcached is available
155 *
156 * @author Art <a.molcanovas@gmail.com>
157 * @return boolean
158 */
159 static function is_available() {
160 return class_exists('\Memcached') || class_exists('\Memcache');
161 }
162
163 /**
164 * Clears all items from cache
165 *
166 * @author Art <a.molcanovas@gmail.com>
167 * @return boolean
168 */
169 function purge() {
170 return self::$loaded ? $this->client->flush() : false;
171 }
172
173 /**
174 * Gets a cached value
175 *
176 * @author Art <a.molcanovas@gmail.com>
177 *
178 * @param string $id The value's key
179 *
180 * @return mixed
181 */
182 function get($id) {
183 return self::$loaded ? $this->client->get($id) : false;
184 }
185
186 /**
187 * Sets a cached key/value pair
188 *
189 * @author Art <a.molcanovas@gmail.com>
190 *
191 * @param string $key The key identifier
192 * @param mixed $var The value to set
193 * @param int $expire When to expire the set data. Defaults to 3600s.
194 *
195 * @return boolean
196 */
197 function set($key, $var, $expire = 3600) {
198 \Log::debug('Set the MemcachedWrapper key ' . $key);
199
200 if(self::$loaded === self::CLASS_MEMCACHED) {
201 return $this->client->set($key, $var, $expire);
202 } elseif(self::$loaded === self::CLASS_MEMCACHE) {
203 return $this->client->set($key, $var, null, $expire);
204 } else {
205 return false;
206 }
207 }
208
209 /**
210 * The memcached version of getAll()
211 *
212 * @author Art <a.molcanovas@gmail.com>
213 * @return array
214 */
215 protected function getAllMemcached() {
216 $keys = $this->client->getAllKeys();
217 $vals = [];
218
219 foreach($keys as $k) {
220 $vals[$k] = $this->get($k);
221 }
222
223 return $vals;
224 }
225
226 /**
227 * The Memcache version of getAll()
228 *
229 * @author Art <a.molcanovas@gmail.com>
230 * @return array
231 */
232 protected function getAllMemcache() {
233 $dump = [];
234 $slabs = $this->client->getextendedstats('slabs');
235
236 foreach($slabs as $serverSlabs) {
237 $keys = array_keys($serverSlabs);
238
239 foreach($keys as $k) {
240 if(is_numeric($k)) {
241 try {
242 $d = $this->client->getextendedstats('cachedump', (int)$k, 1000);
243
244 foreach($d as $data) {
245 if($data) {
246 foreach($data as $mc_key => $row) {
247 $dump[$mc_key] = $row[0];
248 }
249 }
250 }
251 } catch(\Exception $e) {
252 continue;
253 }
254 }
255 }
256 }
257
258 return $dump;
259 }
260
261 /**
262 * Return all cached keys and values
263 *
264 * @author Art <a.molcanovas@gmail.com>
265 * @return array
266 */
267 function getAll() {
268 if(self::$loaded === self::CLASS_MEMCACHED) {
269 return $this->getAllMemcached();
270 } elseif(self::$loaded === self::CLASS_MEMCACHE) {
271 return $this->getAllMemcache();
272 } else {
273 return [];
274 }
275 }
276
277 }