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