APIv4 - Add Address::getCoordinates action
[civicrm-core.git] / CRM / Utils / Cache / Redis.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17 class CRM_Utils_Cache_Redis implements CRM_Utils_Cache_Interface {
18
19 // TODO Consider native implementation.
20 use CRM_Utils_Cache_NaiveMultipleTrait;
21 // TODO Native implementation
22 use CRM_Utils_Cache_NaiveHasTrait;
23
24 const DEFAULT_HOST = 'localhost';
25 const DEFAULT_PORT = 6379;
26 const DEFAULT_TIMEOUT = 3600;
27 const DEFAULT_PREFIX = '';
28
29 /**
30 * The default timeout to use
31 *
32 * @var int
33 */
34 protected $_timeout = self::DEFAULT_TIMEOUT;
35
36 /**
37 * The prefix prepended to cache keys.
38 *
39 * If we are using the same redis instance for multiple CiviCRM
40 * installs, we must have a unique prefix for each install to prevent
41 * the keys from clobbering each other.
42 *
43 * @var string
44 */
45 protected $_prefix = self::DEFAULT_PREFIX;
46
47 /**
48 * The actual redis object
49 *
50 * @var Redis
51 */
52 protected $_cache;
53
54 /**
55 * Create a connection. If a connection already exists, re-use it.
56 *
57 * @param array $config
58 * @return Redis
59 */
60 public static function connect($config) {
61 $host = $config['host'] ?? self::DEFAULT_HOST;
62 $port = $config['port'] ?? self::DEFAULT_PORT;
63 // Ugh.
64 $pass = CRM_Utils_Constant::value('CIVICRM_DB_CACHE_PASSWORD');
65 $id = implode(':', ['connect', $host, $port /* $pass is constant */]);
66 if (!isset(Civi::$statics[__CLASS__][$id])) {
67 // Ideally, we'd track the connection in the service-container, but the
68 // cache connection is boot-critical.
69 $redis = new Redis();
70 if (!$redis->connect($host, $port)) {
71 // dont use fatal here since we can go in an infinite loop
72 echo 'Could not connect to redisd server';
73 CRM_Utils_System::civiExit();
74 }
75 if ($pass) {
76 $redis->auth($pass);
77 }
78 Civi::$statics[__CLASS__][$id] = $redis;
79 }
80 return Civi::$statics[__CLASS__][$id];
81 }
82
83 /**
84 * Constructor
85 *
86 * @param array $config
87 * An array of configuration params.
88 *
89 * @return \CRM_Utils_Cache_Redis
90 */
91 public function __construct($config) {
92 if (isset($config['timeout'])) {
93 $this->_timeout = $config['timeout'];
94 }
95 if (isset($config['prefix'])) {
96 $this->_prefix = $config['prefix'];
97 }
98
99 $this->_cache = self::connect($config);
100 }
101
102 /**
103 * @param $key
104 * @param $value
105 * @param null|int|\DateInterval $ttl
106 *
107 * @return bool
108 * @throws Exception
109 */
110 public function set($key, $value, $ttl = NULL) {
111 CRM_Utils_Cache::assertValidKey($key);
112 if (is_int($ttl) && $ttl <= 0) {
113 return $this->delete($key);
114 }
115 $ttl = CRM_Utils_Date::convertCacheTtl($ttl, self::DEFAULT_TIMEOUT);
116 if (!$this->_cache->setex($this->_prefix . $key, $ttl, serialize($value))) {
117 if (PHP_SAPI === 'cli' || (Civi\Core\Container::isContainerBooted() && CRM_Core_Permission::check('view debug output'))) {
118 throw new CRM_Utils_Cache_CacheException("Redis set ($key) failed: " . $this->_cache->getLastError());
119 }
120 else {
121 Civi::log()->error("Redis set ($key) failed: " . $this->_cache->getLastError());
122 throw new CRM_Utils_Cache_CacheException("Redis set ($key) failed");
123 }
124 return FALSE;
125 }
126 return TRUE;
127 }
128
129 /**
130 * @param $key
131 * @param mixed $default
132 *
133 * @return mixed
134 */
135 public function get($key, $default = NULL) {
136 CRM_Utils_Cache::assertValidKey($key);
137 $result = $this->_cache->get($this->_prefix . $key);
138 return ($result === FALSE) ? $default : unserialize($result);
139 }
140
141 /**
142 * @param $key
143 *
144 * @return bool
145 */
146 public function delete($key) {
147 CRM_Utils_Cache::assertValidKey($key);
148 $this->_cache->del($this->_prefix . $key);
149 return TRUE;
150 }
151
152 /**
153 * @return bool
154 */
155 public function flush() {
156 // FIXME: Ideally, we'd map each prefix to a different 'hash' object in Redis,
157 // and this would be simpler. However, that needs to go in tandem with a
158 // more general rethink of cache expiration/TTL.
159
160 $keys = $this->_cache->keys($this->_prefix . '*');
161 $this->_cache->del($keys);
162 return TRUE;
163 }
164
165 public function clear() {
166 return $this->flush();
167 }
168
169 }