_host = $config['host']; } if (isset($config['port'])) { $this->_port = $config['port']; } if (isset($config['timeout'])) { $this->_timeout = $config['timeout']; } if (isset($config['prefix'])) { $this->_prefix = $config['prefix']; } $this->_cache = new Memcached(); if (!$this->_cache->addServer($this->_host, $this->_port)) { // dont use fatal here since we can go in an infinite loop echo 'Could not connect to Memcached server'; CRM_Utils_System::civiExit(); } } /** * @param $key * @param $value * @param null|int|\DateInterval $ttl * * @return bool * @throws Exception */ public function set($key, $value, $ttl = NULL) { CRM_Utils_Cache::assertValidKey($key); if (is_int($ttl) && $ttl <= 0) { return $this->delete($key); } $expires = CRM_Utils_Date::convertCacheTtlToExpires($ttl, $this->_timeout); $key = $this->cleanKey($key); if (!$this->_cache->set($key, serialize($value), $expires)) { if (PHP_SAPI === 'cli' || (Civi\Core\Container::isContainerBooted() && CRM_Core_Permission::check('view debug output'))) { throw new CRM_Utils_Cache_CacheException("Memcached::set($key) failed: " . $this->_cache->getResultMessage()); } else { Civi::log()->error("Memcached::set($key) failed: " . $this->_cache->getResultMessage()); throw new CRM_Utils_Cache_CacheException("Memcached::set($key) failed"); } return FALSE; } return TRUE; } /** * @param $key * @param mixed $default * * @return mixed */ public function get($key, $default = NULL) { CRM_Utils_Cache::assertValidKey($key); $key = $this->cleanKey($key); $result = $this->_cache->get($key); switch ($this->_cache->getResultCode()) { case Memcached::RES_SUCCESS: return unserialize($result); case Memcached::RES_NOTFOUND: return $default; default: Civi::log()->error("Memcached::get($key) failed: " . $this->_cache->getResultMessage()); throw new CRM_Utils_Cache_CacheException("Memcached set ($key) failed"); } } /** * @param string $key * * @return bool * @throws \Psr\SimpleCache\CacheException */ public function has($key) { CRM_Utils_Cache::assertValidKey($key); $key = $this->cleanKey($key); if ($this->_cache->get($key) !== FALSE) { return TRUE; } switch ($this->_cache->getResultCode()) { case Memcached::RES_NOTFOUND: return FALSE; case Memcached::RES_SUCCESS: return TRUE; default: Civi::log()->error("Memcached::has($key) failed: " . $this->_cache->getResultMessage()); throw new CRM_Utils_Cache_CacheException("Memcached set ($key) failed"); } } /** * @param $key * * @return mixed */ public function delete($key) { CRM_Utils_Cache::assertValidKey($key); $key = $this->cleanKey($key); if ($this->_cache->delete($key)) { return TRUE; } $code = $this->_cache->getResultCode(); return ($code == Memcached::RES_DELETED || $code == Memcached::RES_NOTFOUND); } /** * @param $key * * @return mixed|string */ public function cleanKey($key) { $truePrefix = $this->getTruePrefix(); $maxLen = self::MAX_KEY_LEN - strlen($truePrefix); $key = preg_replace('/\s+|\W+/', '_', $key); if (strlen($key) > $maxLen) { // this should be 32 characters in length $md5Key = md5($key); $subKeyLen = $maxLen - 1 - strlen($md5Key); $key = substr($key, 0, $subKeyLen) . "_" . $md5Key; } return $truePrefix . $key; } /** * @return bool */ public function flush() { $this->_truePrefix = NULL; if ($this->_cache->delete($this->_prefix)) { return TRUE; } $code = $this->_cache->getResultCode(); return ($code == Memcached::RES_DELETED || $code == Memcached::RES_NOTFOUND); } 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 ($this->_cache->getResultCode() === Memcached::RES_NOTFOUND) { $value = uniqid(); // Indefinite. $this->_cache->add($key, $value, 0); } $this->_truePrefix = [ 'value' => $value, 'expires' => time() + self::NS_LOCAL_TTL, ]; } return $this->_prefix . $this->_truePrefix['value'] . '/'; } }