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