]> git.mxchange.org Git - friendica.git/blob - src/Core/Cache/RedisCache.php
Merge pull request #8277 from MrPetovan/task/8251-use-about-for-pdesc
[friendica.git] / src / Core / Cache / RedisCache.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2020, Friendica
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Core\Cache;
23
24 use Exception;
25 use Friendica\Core\BaseCache;
26 use Friendica\Core\Config\IConfig;
27 use Redis;
28
29 /**
30  * Redis Cache. This driver is based on Memcache driver
31  */
32 class RedisCache extends BaseCache implements IMemoryCache
33 {
34         /**
35          * @var Redis
36          */
37         private $redis;
38
39         /**
40          * @throws Exception
41          */
42         public function __construct(string $hostname, IConfig $config)
43         {
44                 if (!class_exists('Redis', false)) {
45                         throw new Exception('Redis class isn\'t available');
46                 }
47
48                 parent::__construct($hostname);
49
50                 $this->redis = new Redis();
51
52                 $redis_host = $config->get('system', 'redis_host');
53                 $redis_port = $config->get('system', 'redis_port');
54                 $redis_pw   = $config->get('system', 'redis_password');
55                 $redis_db   = $config->get('system', 'redis_db', 0);
56
57                 if (isset($redis_port) && !@$this->redis->connect($redis_host, $redis_port)) {
58                         throw new Exception('Expected Redis server at ' . $redis_host . ':' . $redis_port . ' isn\'t available');
59                 } elseif (!@$this->redis->connect($redis_host)) {
60                         throw new Exception('Expected Redis server at ' . $redis_host . ' isn\'t available');
61                 }
62
63                 if (isset($redis_pw) && !$this->redis->auth($redis_pw)) {
64                         throw new Exception('Cannot authenticate redis server at ' . $redis_host . ':' . $redis_port);
65                 }
66
67                 if ($redis_db !== 0 && !$this->redis->select($redis_db)) {
68                         throw new Exception('Cannot switch to redis db ' . $redis_db . ' at ' . $redis_host . ':' . $redis_port);
69                 }
70         }
71
72         /**
73          * (@inheritdoc)
74          */
75         public function getAllKeys($prefix = null)
76         {
77                 if (empty($prefix)) {
78                         $search = '*';
79                 } else {
80                         $search = $prefix . '*';
81                 }
82
83                 $list = $this->redis->keys($this->getCacheKey($search));
84
85                 return $this->getOriginalKeys($list);
86         }
87
88         /**
89          * (@inheritdoc)
90          */
91         public function get($key)
92         {
93                 $return = null;
94                 $cachekey = $this->getCacheKey($key);
95
96                 $cached = $this->redis->get($cachekey);
97                 if ($cached === false && !$this->redis->exists($cachekey)) {
98                         return null;
99                 }
100
101                 $value = unserialize($cached);
102
103                 // Only return a value if the serialized value is valid.
104                 // We also check if the db entry is a serialized
105                 // boolean 'false' value (which we want to return).
106                 if ($cached === serialize(false) || $value !== false) {
107                         $return = $value;
108                 }
109
110                 return $return;
111         }
112
113         /**
114          * (@inheritdoc)
115          */
116         public function set($key, $value, $ttl = Duration::FIVE_MINUTES)
117         {
118                 $cachekey = $this->getCacheKey($key);
119
120                 $cached = serialize($value);
121
122                 if ($ttl > 0) {
123                         return $this->redis->setex(
124                                 $cachekey,
125                                 $ttl,
126                                 $cached
127                         );
128                 } else {
129                         return $this->redis->set(
130                                 $cachekey,
131                                 $cached
132                         );
133                 }
134         }
135
136         /**
137          * (@inheritdoc)
138          */
139         public function delete($key)
140         {
141                 $cachekey = $this->getCacheKey($key);
142                 return ($this->redis->del($cachekey) > 0);
143         }
144
145         /**
146          * (@inheritdoc)
147          */
148         public function clear($outdated = true)
149         {
150                 if ($outdated) {
151                         return true;
152                 } else {
153                         return $this->redis->flushAll();
154                 }
155         }
156
157         /**
158          * (@inheritdoc)
159          */
160         public function add($key, $value, $ttl = Duration::FIVE_MINUTES)
161         {
162                 $cachekey = $this->getCacheKey($key);
163                 $cached = serialize($value);
164
165                 return $this->redis->setnx($cachekey, $cached);
166         }
167
168         /**
169          * (@inheritdoc)
170          */
171         public function compareSet($key, $oldValue, $newValue, $ttl = Duration::FIVE_MINUTES)
172         {
173                 $cachekey = $this->getCacheKey($key);
174
175                 $newCached = serialize($newValue);
176
177                 $this->redis->watch($cachekey);
178                 // If the old value isn't what we expected, somebody else changed the key meanwhile
179                 if ($this->get($key) === $oldValue) {
180                         if ($ttl > 0) {
181                                 $result = $this->redis->multi()
182                                         ->setex($cachekey, $ttl, $newCached)
183                                         ->exec();
184                         } else {
185                                 $result = $this->redis->multi()
186                                         ->set($cachekey, $newCached)
187                                         ->exec();
188                         }
189                         return $result !== false;
190                 }
191                 $this->redis->unwatch();
192                 return false;
193         }
194
195         /**
196          * (@inheritdoc)
197          */
198         public function compareDelete($key, $value)
199         {
200                 $cachekey = $this->getCacheKey($key);
201
202                 $this->redis->watch($cachekey);
203                 // If the old value isn't what we expected, somebody else changed the key meanwhile
204                 if ($this->get($key) === $value) {
205                         $result = $this->redis->multi()
206                                 ->del($cachekey)
207                                 ->exec();
208                         return $result !== false;
209                 }
210                 $this->redis->unwatch();
211                 return false;
212         }
213
214         /**
215          * {@inheritDoc}
216          */
217         public function getName()
218         {
219                 return Type::REDIS;
220         }
221 }