3 namespace Friendica\Core\Cache;
6 use Friendica\Core\Config\Configuration;
8 use Psr\Log\LoggerInterface;
13 * @author Hypolite Petovan <hypolite@mrpetovan.com>
15 class MemcachedCache extends Cache implements IMemoryCache
18 use TraitCompareDelete;
26 * @var LoggerInterface
31 * @var string First server address
37 * @var int First server port
42 * Due to limitations of the INI format, the expected configuration for Memcached servers is the following:
44 * 0 => "hostname, port(, weight)",
48 * @param array $memcached_hosts
52 public function __construct(string $hostname, Configuration $config, LoggerInterface $logger)
54 if (!class_exists('Memcached', false)) {
55 throw new Exception('Memcached class isn\'t available');
58 parent::__construct($hostname);
60 $this->logger = $logger;
62 $this->memcached = new Memcached();
64 $memcached_hosts = $config->get('system', 'memcached_hosts');
66 array_walk($memcached_hosts, function (&$value) {
67 if (is_string($value)) {
68 $value = array_map('trim', explode(',', $value));
72 $this->firstServer = $memcached_hosts[0][0] ?? 'localhost';
73 $this->firstPort = $memcached_hosts[0][1] ?? 11211;
75 $this->memcached->addServers($memcached_hosts);
77 if (count($this->memcached->getServerList()) == 0) {
78 throw new Exception('Expected Memcached servers aren\'t available, config:' . var_export($memcached_hosts, true));
85 public function getAllKeys($prefix = null)
87 $keys = $this->getOriginalKeys($this->getMemcachedKeys());
89 return $this->filterArrayKeysByPrefix($keys, $prefix);
93 * Get all memcached keys.
94 * Special function because getAllKeys() is broken since memcached 1.4.23.
96 * cleaned up version of code found on Stackoverflow.com by Maduka Jayalath
98 * @return array|int - all retrieved keys (or negative number on error)
100 private function getMemcachedKeys()
102 $mem = @fsockopen($this->firstServer, $this->firstPort);
103 if ($mem === false) {
107 // retrieve distinct slab
108 $r = @fwrite($mem, 'stats items' . chr(10));
114 while (($l = @fgets($mem, 1024)) !== false) {
122 // <STAT items:22:evicted_nonzero 0>
123 $r = preg_match('/^STAT\sitems\:(\d+)\:/', $l, $m);
129 if (!array_key_exists($a_slab, $slab)) {
135 foreach ($slab as $a_slab_key => &$a_slab) {
136 $r = @fwrite($mem, 'stats cachedump ' . $a_slab_key . ' 100' . chr(10));
141 while (($l = @fgets($mem, 1024)) !== false) {
149 // ITEM 42 [118 b; 1354717302 s]
150 $r = preg_match('/^ITEM\s([^\s]+)\s/', $l, $m);
160 // close the connection
166 foreach ($slab AS &$a_slab) {
168 foreach ($a_slab AS &$a_key) {
180 public function get($key)
183 $cachekey = $this->getCacheKey($key);
185 // We fetch with the hostname as key to avoid problems with other applications
186 $value = $this->memcached->get($cachekey);
188 if ($this->memcached->getResultCode() === Memcached::RES_SUCCESS) {
191 $this->logger->debug('Memcached \'get\' failed', ['result' => $this->memcached->getResultMessage()]);
200 public function set($key, $value, $ttl = Cache::FIVE_MINUTES)
202 $cachekey = $this->getCacheKey($key);
204 // We store with the hostname as key to avoid problems with other applications
206 return $this->memcached->set(
212 return $this->memcached->set(
222 public function delete($key)
224 $cachekey = $this->getCacheKey($key);
225 return $this->memcached->delete($cachekey);
231 public function clear($outdated = true)
236 return $this->memcached->flush();
243 public function add($key, $value, $ttl = Cache::FIVE_MINUTES)
245 $cachekey = $this->getCacheKey($key);
246 return $this->memcached->add($cachekey, $value, $ttl);
252 public function getName()
254 return self::TYPE_MEMCACHED;