Joomla Platform  13.1
Documentation des API du framework Joomla Platform
 Tout Classes Espaces de nommage Fichiers Fonctions Variables Pages
memcache.php
Aller à la documentation de ce fichier.
1 <?php
2 /**
3  * @package Joomla.Platform
4  * @subpackage Cache
5  *
6  * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
7  * @license GNU General Public License version 2 or later; see LICENSE
8  */
9 
10 defined('JPATH_PLATFORM') or die;
11 
12 /**
13  * Memcache cache storage handler
14  *
15  * @package Joomla.Platform
16  * @subpackage Cache
17  * @see http://php.net/manual/en/book.memcache.php
18  * @since 11.1
19  */
21 {
22  /**
23  * Memcache connection object
24  *
25  * @var Memcache
26  * @since 11.1
27  */
28  protected static $_db = null;
29 
30  /**
31  * Persistent session flag
32  *
33  * @var boolean
34  * @since 11.1
35  */
36  protected $_persistent = false;
37 
38  /**
39  * Payload compression level
40  *
41  * @var integer
42  * @since 11.1
43  */
44  protected $_compress = 0;
45 
46  /**
47  * Constructor
48  *
49  * @param array $options Optional parameters.
50  *
51  * @since 11.1
52  */
53  public function __construct($options = array())
54  {
55  parent::__construct($options);
56  if (self::$_db === null)
57  {
58  $this->getConnection();
59  }
60  }
61 
62  /**
63  * Return memcache connection object
64  *
65  * @return object memcache connection object
66  *
67  * @since 11.1
68  * @throws RuntimeException
69  */
70  protected function getConnection()
71  {
72  if ((extension_loaded('memcache') && class_exists('Memcache')) != true)
73  {
74  return false;
75  }
76 
77  $config = JFactory::getConfig();
78  $this->_persistent = $config->get('memcache_persist', true);
79  $this->_compress = $config->get('memcache_compress', false) == false ? 0 : MEMCACHE_COMPRESSED;
80 
81  /*
82  * This will be an array of loveliness
83  * @todo: multiple servers
84  * $servers = (isset($params['servers'])) ? $params['servers'] : array();
85  */
86  $server = array();
87  $server['host'] = $config->get('memcache_server_host', 'localhost');
88  $server['port'] = $config->get('memcache_server_port', 11211);
89 
90  // Create the memcache connection
91  self::$_db = new Memcache;
92  self::$_db->addServer($server['host'], $server['port'], $this->_persistent);
93 
94  $memcachetest = @self::$_db->connect($server['host'], $server['port']);
95  if ($memcachetest == false)
96  {
97  throw new RuntimeException('Could not connect to memcache server', 404);
98  }
99 
100  // Memcahed has no list keys, we do our own accounting, initialise key index
101  if (self::$_db->get($this->_hash . '-index') === false)
102  {
103  $empty = array();
104  self::$_db->set($this->_hash . '-index', $empty, $this->_compress, 0);
105  }
106 
107  return;
108  }
109 
110  /**
111  * Get cached data from memcache by id and group
112  *
113  * @param string $id The cache data id
114  * @param string $group The cache data group
115  * @param boolean $checkTime True to verify cache time expiration threshold
116  *
117  * @return mixed Boolean false on failure or a cached data string
118  *
119  * @since 11.1
120  */
121  public function get($id, $group, $checkTime = true)
122  {
123  $cache_id = $this->_getCacheId($id, $group);
124  $back = self::$_db->get($cache_id);
125  return $back;
126  }
127 
128  /**
129  * Get all cached data
130  *
131  * @return array data
132  *
133  * @since 11.1
134  */
135  public function getAll()
136  {
137  parent::getAll();
138 
139  $keys = self::$_db->get($this->_hash . '-index');
140  $secret = $this->_hash;
141 
142  $data = array();
143 
144  if (!empty($keys))
145  {
146  foreach ($keys as $key)
147  {
148  if (empty($key))
149  {
150  continue;
151  }
152  $namearr = explode('-', $key->name);
153 
154  if ($namearr !== false && $namearr[0] == $secret && $namearr[1] == 'cache')
155  {
156 
157  $group = $namearr[2];
158 
159  if (!isset($data[$group]))
160  {
161  $item = new JCacheStorageHelper($group);
162  }
163  else
164  {
165  $item = $data[$group];
166  }
167 
168  $item->updateSize($key->size / 1024);
169 
170  $data[$group] = $item;
171  }
172  }
173  }
174 
175  return $data;
176  }
177 
178  /**
179  * Store the data to memcache by id and group
180  *
181  * @param string $id The cache data id
182  * @param string $group The cache data group
183  * @param string $data The data to store in cache
184  *
185  * @return boolean True on success, false otherwise
186  *
187  * @since 11.1
188  */
189  public function store($id, $group, $data)
190  {
191  $cache_id = $this->_getCacheId($id, $group);
192 
193  if (!$this->lockindex())
194  {
195  return false;
196  }
197 
198  $index = self::$_db->get($this->_hash . '-index');
199  if ($index === false)
200  {
201  $index = array();
202  }
203 
204  $tmparr = new stdClass;
205  $tmparr->name = $cache_id;
206  $tmparr->size = strlen($data);
207 
208  $config = JFactory::getConfig();
209  $lifetime = (int) $config->get('cachetime', 15);
210  if ($this->_lifetime == $lifetime)
211  {
212  $this->_lifetime = $lifetime * 60;
213  }
214 
215  $index[] = $tmparr;
216  self::$_db->replace($this->_hash . '-index', $index, 0, 0);
217  $this->unlockindex();
218 
219  // Prevent double writes, write only if it doesn't exist else replace
220  if (!self::$_db->replace($cache_id, $data, $this->_compress, $this->_lifetime))
221  {
222  self::$_db->set($cache_id, $data, $this->_compress, $this->_lifetime);
223  }
224 
225  return true;
226  }
227 
228  /**
229  * Remove a cached data entry by id and group
230  *
231  * @param string $id The cache data id
232  * @param string $group The cache data group
233  *
234  * @return boolean True on success, false otherwise
235  *
236  * @since 11.1
237  */
238  public function remove($id, $group)
239  {
240  $cache_id = $this->_getCacheId($id, $group);
241 
242  if (!$this->lockindex())
243  {
244  return false;
245  }
246 
247  $index = self::$_db->get($this->_hash . '-index');
248  if ($index === false)
249  {
250  $index = array();
251  }
252 
253  foreach ($index as $key => $value)
254  {
255  if ($value->name == $cache_id)
256  {
257  unset($index[$key]);
258  }
259  break;
260  }
261  self::$_db->replace($this->_hash . '-index', $index, 0, 0);
262  $this->unlockindex();
263 
264  return self::$_db->delete($cache_id);
265  }
266 
267  /**
268  * Clean cache for a group given a mode.
269  *
270  * @param string $group The cache data group
271  * @param string $mode The mode for cleaning cache [group|notgroup]
272  * group mode : cleans all cache in the group
273  * notgroup mode : cleans all cache not in the group
274  *
275  * @return boolean True on success, false otherwise
276  *
277  * @since 11.1
278  */
279  public function clean($group, $mode = null)
280  {
281  if (!$this->lockindex())
282  {
283  return false;
284  }
285 
286  $index = self::$_db->get($this->_hash . '-index');
287  if ($index === false)
288  {
289  $index = array();
290  }
291 
292  $secret = $this->_hash;
293  foreach ($index as $key => $value)
294  {
295 
296  if (strpos($value->name, $secret . '-cache-' . $group . '-') === 0 xor $mode != 'group')
297  {
298  self::$_db->delete($value->name, 0);
299  unset($index[$key]);
300  }
301  }
302  self::$_db->replace($this->_hash . '-index', $index, 0, 0);
303  $this->unlockindex();
304  return true;
305  }
306 
307  /**
308  * Test to see if the cache storage is available.
309  *
310  * @return boolean True on success, false otherwise.
311  *
312  * @since 12.1
313  */
314  public static function isSupported()
315  {
316  if ((extension_loaded('memcache') && class_exists('Memcache')) != true)
317  {
318  return false;
319  }
320 
321  $config = JFactory::getConfig();
322  $host = $config->get('memcache_server_host', 'localhost');
323  $port = $config->get('memcache_server_port', 11211);
324 
325  $memcache = new Memcache;
326  $memcachetest = @$memcache->connect($host, $port);
327 
328  if (!$memcachetest)
329  {
330  return false;
331  }
332  else
333  {
334  return true;
335  }
336  }
337 
338  /**
339  * Lock cached item - override parent as this is more efficient
340  *
341  * @param string $id The cache data id
342  * @param string $group The cache data group
343  * @param integer $locktime Cached item max lock time
344  *
345  * @return boolean True on success, false otherwise.
346  *
347  * @since 11.1
348  */
349  public function lock($id, $group, $locktime)
350  {
351  $returning = new stdClass;
352  $returning->locklooped = false;
353 
354  $looptime = $locktime * 10;
355 
356  $cache_id = $this->_getCacheId($id, $group);
357 
358  if (!$this->lockindex())
359  {
360  return false;
361  }
362 
363  $index = self::$_db->get($this->_hash . '-index');
364  if ($index === false)
365  {
366  $index = array();
367  }
368 
369  $tmparr = new stdClass;
370  $tmparr->name = $cache_id;
371  $tmparr->size = 1;
372  $index[] = $tmparr;
373  self::$_db->replace($this->_hash . '-index', $index, 0, 0);
374  $this->unlockindex();
375 
376  $data_lock = self::$_db->add($cache_id . '_lock', 1, false, $locktime);
377 
378  if ($data_lock === false)
379  {
380 
381  $lock_counter = 0;
382 
383  // Loop until you find that the lock has been released.
384  // That implies that data get from other thread has finished
385  while ($data_lock === false)
386  {
387 
388  if ($lock_counter > $looptime)
389  {
390  $returning->locked = false;
391  $returning->locklooped = true;
392  break;
393  }
394 
395  usleep(100);
396  $data_lock = self::$_db->add($cache_id . '_lock', 1, false, $locktime);
397  $lock_counter++;
398  }
399 
400  }
401  $returning->locked = $data_lock;
402 
403  return $returning;
404  }
405 
406  /**
407  * Unlock cached item - override parent for cacheid compatibility with lock
408  *
409  * @param string $id The cache data id
410  * @param string $group The cache data group
411  *
412  * @return boolean True on success, false otherwise.
413  *
414  * @since 11.1
415  */
416  public function unlock($id, $group = null)
417  {
418  $cache_id = $this->_getCacheId($id, $group) . '_lock';
419 
420  if (!$this->lockindex())
421  {
422  return false;
423  }
424 
425  $index = self::$_db->get($this->_hash . '-index');
426  if ($index === false)
427  {
428  $index = array();
429  }
430 
431  foreach ($index as $key => $value)
432  {
433  if ($value->name == $cache_id)
434  {
435  unset($index[$key]);
436  }
437  break;
438  }
439  self::$_db->replace($this->_hash . '-index', $index, 0, 0);
440  $this->unlockindex();
441 
442  return self::$_db->delete($cache_id);
443  }
444 
445  /**
446  * Lock cache index
447  *
448  * @return boolean True on success, false otherwise.
449  *
450  * @since 11.1
451  */
452  protected function lockindex()
453  {
454  $looptime = 300;
455  $data_lock = self::$_db->add($this->_hash . '-index_lock', 1, false, 30);
456 
457  if ($data_lock === false)
458  {
459 
460  $lock_counter = 0;
461 
462  // Loop until you find that the lock has been released. that implies that data get from other thread has finished
463  while ($data_lock === false)
464  {
465  if ($lock_counter > $looptime)
466  {
467  return false;
468  break;
469  }
470 
471  usleep(100);
472  $data_lock = self::$_db->add($this->_hash . '-index_lock', 1, false, 30);
473  $lock_counter++;
474  }
475  }
476 
477  return true;
478  }
479 
480  /**
481  * Unlock cache index
482  *
483  * @return boolean True on success, false otherwise.
484  *
485  * @since 11.1
486  */
487  protected function unlockindex()
488  {
489  return self::$_db->delete($this->_hash . '-index_lock');
490  }
491 }