+--------------------------------------------------------------------+
| CiviCRM version 5 |
+--------------------------------------------------------------------+
- | Copyright CiviCRM LLC (c) 2004-2018 |
+ | Copyright CiviCRM LLC (c) 2004-2019 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
/**
*
* @package CRM
- * @copyright CiviCRM LLC (c) 2004-2018
+ * @copyright CiviCRM LLC (c) 2004-2019
*/
class CRM_Utils_Cache_Memcache implements CRM_Utils_Cache_Interface {
- use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation.
+ // TODO Consider native implementation.
+ use CRM_Utils_Cache_NaiveMultipleTrait;
const DEFAULT_HOST = 'localhost';
const DEFAULT_PORT = 11211;
const DEFAULT_TIMEOUT = 3600;
const DEFAULT_PREFIX = '';
+ /**
+ * If another process clears namespace, we'll find out in ~5 sec.
+ */
+ const NS_LOCAL_TTL = 5;
+
/**
* The host name of the memcached server.
*
*/
protected $_cache;
+ /**
+ * @var NULL|array
+ *
+ * This is the effective prefix. It may be bumped up whenever the dataset is flushed.
+ *
+ * @see https://github.com/memcached/memcached/wiki/ProgrammingTricks#deleting-by-namespace
+ */
+ protected $_truePrefix = NULL;
+
/**
* Constructor.
*
* @return bool
*/
public function set($key, $value, $ttl = NULL) {
- if ($ttl !== NULL) {
- throw new \RuntimeException("FIXME: " . __CLASS__ . "::set() should support non-NULL TTL");
- }
- if (!$this->_cache->set($this->_prefix . $key, $value, FALSE, $this->_timeout)) {
- return FALSE;
+ CRM_Utils_Cache::assertValidKey($key);
+ if (is_int($ttl) && $ttl <= 0) {
+ return $this->delete($key);
}
- return TRUE;
+ $expires = CRM_Utils_Date::convertCacheTtlToExpires($ttl, $this->_timeout);
+ return $this->_cache->set($this->getTruePrefix() . $key, serialize($value), FALSE, $expires);
}
/**
* @return mixed
*/
public function get($key, $default = NULL) {
- if ($default !== NULL) {
- throw new \RuntimeException("FIXME: " . __CLASS__ . "::get() only supports NULL default");
- }
- $result = $this->_cache->get($this->_prefix . $key);
- return $result;
+ CRM_Utils_Cache::assertValidKey($key);
+ $result = $this->_cache->get($this->getTruePrefix() . $key);
+ return ($result === FALSE) ? $default : unserialize($result);
+ }
+
+ /**
+ * @param string $key
+ *
+ * @return bool
+ * @throws \Psr\SimpleCache\CacheException
+ */
+ public function has($key) {
+ CRM_Utils_Cache::assertValidKey($key);
+ $result = $this->_cache->get($this->getTruePrefix() . $key);
+ return ($result !== FALSE);
}
/**
* @param $key
*
- * @return mixed
+ * @return bool
*/
public function delete($key) {
- return $this->_cache->delete($this->_prefix . $key);
+ CRM_Utils_Cache::assertValidKey($key);
+ $this->_cache->delete($this->getTruePrefix() . $key);
+ return TRUE;
}
/**
- * @return mixed
+ * @return bool
*/
public function flush() {
- // FIXME: Only delete items matching `$this->_prefix`.
- return $this->_cache->flush();
+ $this->_truePrefix = NULL;
+ $this->_cache->delete($this->_prefix);
+ return TRUE;
+ }
+
+ public function clear() {
+ return $this->flush();
+ }
+
+ protected function getTruePrefix() {
+ if ($this->_truePrefix === NULL || $this->_truePrefix['expires'] < time()) {
+ $key = $this->_prefix;
+ $value = $this->_cache->get($key);
+ if ($value === FALSE) {
+ $value = uniqid();
+ // Indefinite.
+ $this->_cache->set($key, $value, FALSE, 0);
+ }
+ $this->_truePrefix = [
+ 'value' => $value,
+ 'expires' => time() + self::NS_LOCAL_TTL,
+ ];
+ }
+ return $this->_prefix . $this->_truePrefix['value'] . '/';
}
}