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