]> git.mxchange.org Git - friendica.git/blob - src/Core/Cache/MemcachedCache.php
89685c3f255ce44f83d5745e934eea4a6803c4b5
[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          *
98          * @return array|int - all retrieved keys (or negative number on error)
99          */
100         private function getMemcachedKeys()
101         {
102                 $mem = @fsockopen($this->firstServer, $this->firstPort);
103                 if ($mem === false) {
104                         return -1;
105                 }
106
107                 // retrieve distinct slab
108                 $r = @fwrite($mem, 'stats items' . chr(10));
109                 if ($r === false) {
110                         return -2;
111                 }
112
113                 $slab = [];
114                 while (($l = @fgets($mem, 1024)) !== false) {
115                         // finished?
116                         $l = trim($l);
117                         if ($l == 'END') {
118                                 break;
119                         }
120
121                         $m = [];
122                         // <STAT items:22:evicted_nonzero 0>
123                         $r = preg_match('/^STAT\sitems\:(\d+)\:/', $l, $m);
124                         if ($r != 1) {
125                                 return -3;
126                         }
127                         $a_slab = $m[1];
128
129                         if (!array_key_exists($a_slab, $slab)) {
130                                 $slab[$a_slab] = [];
131                         }
132                 }
133
134                 reset($slab);
135                 foreach ($slab as $a_slab_key => &$a_slab) {
136                         $r = @fwrite($mem, 'stats cachedump ' . $a_slab_key . ' 100' . chr(10));
137                         if ($r === false) {
138                                 return -4;
139                         }
140
141                         while (($l = @fgets($mem, 1024)) !== false) {
142                                 // finished?
143                                 $l = trim($l);
144                                 if ($l == 'END') {
145                                         break;
146                                 }
147
148                                 $m = [];
149                                 // ITEM 42 [118 b; 1354717302 s]
150                                 $r = preg_match('/^ITEM\s([^\s]+)\s/', $l, $m);
151                                 if ($r != 1) {
152                                         return -5;
153                                 }
154                                 $a_key = $m[1];
155
156                                 $a_slab[] = $a_key;
157                         }
158                 }
159
160                 // close the connection
161                 @fclose($mem);
162                 unset($mem);
163
164                 $keys = [];
165                 reset($slab);
166                 foreach ($slab AS &$a_slab) {
167                         reset($a_slab);
168                         foreach ($a_slab AS &$a_key) {
169                                 $keys[] = $a_key;
170                         }
171                 }
172                 unset($slab);
173
174                 return $keys;
175         }
176
177         /**
178          * (@inheritdoc)
179          */
180         public function get($key)
181         {
182                 $return   = null;
183                 $cachekey = $this->getCacheKey($key);
184
185                 // We fetch with the hostname as key to avoid problems with other applications
186                 $value = $this->memcached->get($cachekey);
187
188                 if ($this->memcached->getResultCode() === Memcached::RES_SUCCESS) {
189                         $return = $value;
190                 } else {
191                         $this->logger->debug('Memcached \'get\' failed', ['result' => $this->memcached->getResultMessage()]);
192                 }
193
194                 return $return;
195         }
196
197         /**
198          * (@inheritdoc)
199          */
200         public function set($key, $value, $ttl = Cache::FIVE_MINUTES)
201         {
202                 $cachekey = $this->getCacheKey($key);
203
204                 // We store with the hostname as key to avoid problems with other applications
205                 if ($ttl > 0) {
206                         return $this->memcached->set(
207                                 $cachekey,
208                                 $value,
209                                 $ttl
210                         );
211                 } else {
212                         return $this->memcached->set(
213                                 $cachekey,
214                                 $value
215                         );
216                 }
217         }
218
219         /**
220          * (@inheritdoc)
221          */
222         public function delete($key)
223         {
224                 $cachekey = $this->getCacheKey($key);
225                 return $this->memcached->delete($cachekey);
226         }
227
228         /**
229          * (@inheritdoc)
230          */
231         public function clear($outdated = true)
232         {
233                 if ($outdated) {
234                         return true;
235                 } else {
236                         return $this->memcached->flush();
237                 }
238         }
239
240         /**
241          * (@inheritdoc)
242          */
243         public function add($key, $value, $ttl = Cache::FIVE_MINUTES)
244         {
245                 $cachekey = $this->getCacheKey($key);
246                 return $this->memcached->add($cachekey, $value, $ttl);
247         }
248
249         /**
250          * {@inheritDoc}
251          */
252         public function getName()
253         {
254                 return self::TYPE_MEMCACHED;
255         }
256 }