]> git.mxchange.org Git - friendica.git/blob - src/Core/Cache/MemcachedCache.php
69f6b9a0a7c0eea4c1da800ac2ba224c5bf7a058
[friendica.git] / src / Core / Cache / MemcachedCache.php
1 <?php
2
3 namespace Friendica\Core\Cache;
4
5 use Exception;
6 use Friendica\Core\Config\Configuration;
7 use Memcached;
8 use Psr\Log\LoggerInterface;
9
10 /**
11  * Memcached Cache
12  *
13  * @author Hypolite Petovan <hypolite@mrpetovan.com>
14  */
15 class MemcachedCache extends Cache implements IMemoryCache
16 {
17         use TraitCompareSet;
18         use TraitCompareDelete;
19
20         /**
21          * @var \Memcached
22          */
23         private $memcached;
24
25         /**
26          * @var LoggerInterface
27          */
28         private $logger;
29
30         /**
31          * @var string First server address
32          */
33
34         private $firstServer;
35
36         /**
37          * @var int First server port
38          */
39         private $firstPort;
40
41         /**
42          * Due to limitations of the INI format, the expected configuration for Memcached servers is the following:
43          * array {
44          *   0 => "hostname, port(, weight)",
45          *   1 => ...
46          * }
47          *
48          * @param array $memcached_hosts
49          *
50          * @throws \Exception
51          */
52         public function __construct(string $hostname, Configuration $config, LoggerInterface $logger)
53         {
54                 if (!class_exists('Memcached', false)) {
55                         throw new Exception('Memcached class isn\'t available');
56                 }
57
58                 parent::__construct($hostname);
59
60                 $this->logger = $logger;
61
62                 $this->memcached = new Memcached();
63
64                 $memcached_hosts = $config->get('system', 'memcached_hosts');
65
66                 array_walk($memcached_hosts, function (&$value) {
67                         if (is_string($value)) {
68                                 $value = array_map('trim', explode(',', $value));
69                         }
70                 });
71
72                 $this->firstServer = $memcached_hosts[0][0] ?? 'localhost';
73                 $this->firstPort   = $memcached_hosts[0][1] ?? 11211;
74
75                 $this->memcached->addServers($memcached_hosts);
76
77                 if (count($this->memcached->getServerList()) == 0) {
78                         throw new Exception('Expected Memcached servers aren\'t available, config:' . var_export($memcached_hosts, true));
79                 }
80         }
81
82         /**
83          * (@inheritdoc)
84          */
85         public function getAllKeys($prefix = null)
86         {
87                 $keys = $this->getOriginalKeys($this->getMemcachedKeys());
88
89                 return $this->filterArrayKeysByPrefix($keys, $prefix);
90         }
91
92         /**
93          * Get all memcached keys.
94          * Special function because getAllKeys() is broken since memcached 1.4.23.
95          *
96          * cleaned up version of code found on Stackoverflow.com by Maduka Jayalath
97          * @see https://stackoverflow.com/a/34724821
98          *
99          * @return array|int - all retrieved keys (or negative number on error)
100          */
101         private function getMemcachedKeys()
102         {
103                 $mem = @fsockopen($this->firstServer, $this->firstPort);
104                 if ($mem === false) {
105                         return -1;
106                 }
107
108                 // retrieve distinct slab
109                 $r = @fwrite($mem, 'stats items' . chr(10));
110                 if ($r === false) {
111                         return -2;
112                 }
113
114                 $slab = [];
115                 while (($l = @fgets($mem, 1024)) !== false) {
116                         // finished?
117                         $l = trim($l);
118                         if ($l == 'END') {
119                                 break;
120                         }
121
122                         $m = [];
123                         // <STAT items:22:evicted_nonzero 0>
124                         $r = preg_match('/^STAT\sitems\:(\d+)\:/', $l, $m);
125                         if ($r != 1) {
126                                 return -3;
127                         }
128                         $a_slab = $m[1];
129
130                         if (!array_key_exists($a_slab, $slab)) {
131                                 $slab[$a_slab] = [];
132                         }
133                 }
134
135                 reset($slab);
136                 foreach ($slab as $a_slab_key => &$a_slab) {
137                         $r = @fwrite($mem, 'stats cachedump ' . $a_slab_key . ' 100' . chr(10));
138                         if ($r === false) {
139                                 return -4;
140                         }
141
142                         while (($l = @fgets($mem, 1024)) !== false) {
143                                 // finished?
144                                 $l = trim($l);
145                                 if ($l == 'END') {
146                                         break;
147                                 }
148
149                                 $m = [];
150                                 // ITEM 42 [118 b; 1354717302 s]
151                                 $r = preg_match('/^ITEM\s([^\s]+)\s/', $l, $m);
152                                 if ($r != 1) {
153                                         return -5;
154                                 }
155                                 $a_key = $m[1];
156
157                                 $a_slab[] = $a_key;
158                         }
159                 }
160
161                 // close the connection
162                 @fclose($mem);
163                 unset($mem);
164
165                 $keys = [];
166                 reset($slab);
167                 foreach ($slab AS &$a_slab) {
168                         reset($a_slab);
169                         foreach ($a_slab AS &$a_key) {
170                                 $keys[] = $a_key;
171                         }
172                 }
173                 unset($slab);
174
175                 return $keys;
176         }
177
178         /**
179          * (@inheritdoc)
180          */
181         public function get($key)
182         {
183                 $return   = null;
184                 $cachekey = $this->getCacheKey($key);
185
186                 // We fetch with the hostname as key to avoid problems with other applications
187                 $value = $this->memcached->get($cachekey);
188
189                 if ($this->memcached->getResultCode() === Memcached::RES_SUCCESS) {
190                         $return = $value;
191                 } else {
192                         $this->logger->debug('Memcached \'get\' failed', ['result' => $this->memcached->getResultMessage()]);
193                 }
194
195                 return $return;
196         }
197
198         /**
199          * (@inheritdoc)
200          */
201         public function set($key, $value, $ttl = Cache::FIVE_MINUTES)
202         {
203                 $cachekey = $this->getCacheKey($key);
204
205                 // We store with the hostname as key to avoid problems with other applications
206                 if ($ttl > 0) {
207                         return $this->memcached->set(
208                                 $cachekey,
209                                 $value,
210                                 $ttl
211                         );
212                 } else {
213                         return $this->memcached->set(
214                                 $cachekey,
215                                 $value
216                         );
217                 }
218         }
219
220         /**
221          * (@inheritdoc)
222          */
223         public function delete($key)
224         {
225                 $cachekey = $this->getCacheKey($key);
226                 return $this->memcached->delete($cachekey);
227         }
228
229         /**
230          * (@inheritdoc)
231          */
232         public function clear($outdated = true)
233         {
234                 if ($outdated) {
235                         return true;
236                 } else {
237                         return $this->memcached->flush();
238                 }
239         }
240
241         /**
242          * (@inheritdoc)
243          */
244         public function add($key, $value, $ttl = Cache::FIVE_MINUTES)
245         {
246                 $cachekey = $this->getCacheKey($key);
247                 return $this->memcached->add($cachekey, $value, $ttl);
248         }
249
250         /**
251          * {@inheritDoc}
252          */
253         public function getName()
254         {
255                 return self::TYPE_MEMCACHED;
256         }
257 }