Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
bc77d7c0 | 4 | | Copyright CiviCRM LLC. All rights reserved. | |
6a488035 | 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 | | |
6a488035 | 9 | +--------------------------------------------------------------------+ |
d25dd0ee | 10 | */ |
6a488035 TO |
11 | |
12 | /** | |
13 | * | |
14 | * @package CRM | |
ca5cec67 | 15 | * @copyright CiviCRM LLC https://civicrm.org/licensing |
6a488035 TO |
16 | */ |
17 | ||
18 | /** | |
19 | * This caching provider stores all cached items as a "group" in the | |
20 | * "civicrm_cache" table. The entire 'group' may be prefetched when | |
21 | * instantiating the cache provider. | |
22 | */ | |
23 | class CRM_Utils_Cache_SqlGroup implements CRM_Utils_Cache_Interface { | |
24 | ||
be5eea0d TO |
25 | // 6*60*60 |
26 | const DEFAULT_TTL = 21600; | |
27 | ||
28 | const TS_FMT = 'Y-m-d H:i:s'; | |
6714d8d2 SL |
29 | // TODO Consider native implementation. |
30 | use CRM_Utils_Cache_NaiveMultipleTrait; | |
0d64c8fa | 31 | |
6a488035 | 32 | /** |
fe482240 | 33 | * The host name of the memcached server. |
6a488035 TO |
34 | * |
35 | * @var string | |
36 | */ | |
37 | protected $group; | |
38 | ||
39 | /** | |
6714d8d2 | 40 | * @var int |
6a488035 TO |
41 | */ |
42 | protected $componentID; | |
f813f78e | 43 | |
6a488035 | 44 | /** |
041ecc95 | 45 | * In-memory cache to optimize redundant get()s. |
46 | * | |
47 | * @var array | |
6a488035 | 48 | */ |
be5eea0d TO |
49 | protected $valueCache; |
50 | ||
51 | /** | |
041ecc95 | 52 | * In-memory cache to optimize redundant get()s. |
53 | * | |
54 | * @var array | |
be5eea0d TO |
55 | * Note: expiresCache[$key]===NULL means cache-miss |
56 | */ | |
57 | protected $expiresCache; | |
58 | ||
59 | /** | |
041ecc95 | 60 | * Table. |
61 | * | |
be5eea0d TO |
62 | * @var string |
63 | */ | |
64 | protected $table; | |
6a488035 TO |
65 | |
66 | /** | |
fe482240 | 67 | * Constructor. |
6a488035 | 68 | * |
77855840 TO |
69 | * @param array $config |
70 | * An array of configuration params. | |
6a488035 TO |
71 | * - group: string |
72 | * - componentID: int | |
73 | * - prefetch: bool, whether to preemptively read the entire cache group; default: TRUE | |
74 | * | |
77b97be7 EM |
75 | * @throws RuntimeException |
76 | * @return \CRM_Utils_Cache_SqlGroup | |
6a488035 | 77 | */ |
00be9182 | 78 | public function __construct($config) { |
be5eea0d | 79 | $this->table = CRM_Core_DAO_Cache::getTableName(); |
6a488035 TO |
80 | if (isset($config['group'])) { |
81 | $this->group = $config['group']; | |
0db6c3e1 TO |
82 | } |
83 | else { | |
6a488035 TO |
84 | throw new RuntimeException("Cannot construct SqlGroup cache: missing group"); |
85 | } | |
86 | if (isset($config['componentID'])) { | |
87 | $this->componentID = $config['componentID']; | |
0db6c3e1 TO |
88 | } |
89 | else { | |
6a488035 TO |
90 | $this->componentID = NULL; |
91 | } | |
be2fb01f | 92 | $this->valueCache = []; |
6a488035 TO |
93 | if (CRM_Utils_Array::value('prefetch', $config, TRUE)) { |
94 | $this->prefetch(); | |
95 | } | |
96 | } | |
97 | ||
5bc392e6 EM |
98 | /** |
99 | * @param string $key | |
100 | * @param mixed $value | |
858451a9 TO |
101 | * @param null|int|\DateInterval $ttl |
102 | * @return bool | |
5bc392e6 | 103 | */ |
858451a9 | 104 | public function set($key, $value, $ttl = NULL) { |
be5eea0d TO |
105 | CRM_Utils_Cache::assertValidKey($key); |
106 | ||
107 | $lock = Civi::lockManager()->acquire("cache.{$this->group}_{$key}._null"); | |
108 | if (!$lock->isAcquired()) { | |
109 | throw new \CRM_Utils_Cache_CacheException("SqlGroup: Failed to acquire lock on cache key."); | |
110 | } | |
111 | ||
13ca7ebf TO |
112 | if (is_int($ttl) && $ttl <= 0) { |
113 | return $this->delete($key); | |
114 | } | |
115 | ||
be5eea0d | 116 | $dataExists = CRM_Core_DAO::singleValueQuery("SELECT COUNT(*) FROM {$this->table} WHERE {$this->where($key)}"); |
13ca7ebf | 117 | $expires = round(microtime(1)) + CRM_Utils_Date::convertCacheTtl($ttl, self::DEFAULT_TTL); |
be5eea0d TO |
118 | |
119 | $dataSerialized = CRM_Core_BAO_Cache::encode($value); | |
120 | ||
121 | // This table has a wonky index, so we cannot use REPLACE or | |
122 | // "INSERT ... ON DUPE". Instead, use SELECT+(INSERT|UPDATE). | |
123 | if ($dataExists) { | |
124 | $sql = "UPDATE {$this->table} SET data = %1, created_date = FROM_UNIXTIME(%2), expired_date = FROM_UNIXTIME(%3) WHERE {$this->where($key)}"; | |
be2fb01f CW |
125 | $args = [ |
126 | 1 => [$dataSerialized, 'String'], | |
127 | 2 => [time(), 'Positive'], | |
128 | 3 => [$expires, 'Positive'], | |
129 | ]; | |
be5eea0d TO |
130 | $dao = CRM_Core_DAO::executeQuery($sql, $args, FALSE, NULL, FALSE, FALSE); |
131 | } | |
132 | else { | |
133 | $sql = "INSERT INTO {$this->table} (group_name,path,data,created_date,expired_date) VALUES (%1,%2,%3,FROM_UNIXTIME(%4),FROM_UNIXTIME(%5))"; | |
be2fb01f | 134 | $args = [ |
be5eea0d TO |
135 | 1 => [$this->group, 'String'], |
136 | 2 => [$key, 'String'], | |
137 | 3 => [$dataSerialized, 'String'], | |
138 | 4 => [time(), 'Positive'], | |
139 | 5 => [$expires, 'Positive'], | |
be2fb01f | 140 | ]; |
be5eea0d | 141 | $dao = CRM_Core_DAO::executeQuery($sql, $args, FALSE, NULL, FALSE, FALSE); |
858451a9 | 142 | } |
be5eea0d TO |
143 | |
144 | $lock->release(); | |
145 | ||
be5eea0d TO |
146 | $this->valueCache[$key] = CRM_Core_BAO_Cache::decode($dataSerialized); |
147 | $this->expiresCache[$key] = $expires; | |
858451a9 | 148 | return TRUE; |
6a488035 TO |
149 | } |
150 | ||
5bc392e6 EM |
151 | /** |
152 | * @param string $key | |
2da67cc5 | 153 | * @param mixed $default |
5bc392e6 EM |
154 | * |
155 | * @return mixed | |
156 | */ | |
2da67cc5 | 157 | public function get($key, $default = NULL) { |
be5eea0d TO |
158 | CRM_Utils_Cache::assertValidKey($key); |
159 | if (!isset($this->expiresCache[$key]) || time() >= $this->expiresCache[$key]) { | |
160 | $sql = "SELECT path, data, UNIX_TIMESTAMP(expired_date) as expires FROM {$this->table} WHERE " . $this->where($key); | |
161 | $dao = CRM_Core_DAO::executeQuery($sql); | |
162 | while ($dao->fetch()) { | |
163 | $this->expiresCache[$key] = $dao->expires; | |
164 | $this->valueCache[$key] = CRM_Core_BAO_Cache::decode($dao->data); | |
165 | } | |
6a488035 | 166 | } |
be5eea0d TO |
167 | return (isset($this->expiresCache[$key]) && time() < $this->expiresCache[$key]) ? $this->reobjectify($this->valueCache[$key]) : $default; |
168 | } | |
169 | ||
170 | private function reobjectify($value) { | |
171 | return is_object($value) ? unserialize(serialize($value)) : $value; | |
6a488035 TO |
172 | } |
173 | ||
5bc392e6 EM |
174 | /** |
175 | * @param $key | |
176 | * @param null $default | |
177 | * | |
178 | * @return mixed | |
179 | */ | |
00be9182 | 180 | public function getFromFrontCache($key, $default = NULL) { |
be5eea0d TO |
181 | if (isset($this->expiresCache[$key]) && time() < $this->expiresCache[$key] && $this->valueCache[$key]) { |
182 | return $this->reobjectify($this->valueCache[$key]); | |
183 | } | |
184 | else { | |
185 | return $default; | |
186 | } | |
187 | } | |
188 | ||
189 | public function has($key) { | |
190 | $this->get($key); | |
191 | return isset($this->expiresCache[$key]) && time() < $this->expiresCache[$key]; | |
20b015e1 TO |
192 | } |
193 | ||
5bc392e6 EM |
194 | /** |
195 | * @param string $key | |
eec321a4 | 196 | * @return bool |
5bc392e6 | 197 | */ |
00be9182 | 198 | public function delete($key) { |
be5eea0d | 199 | CRM_Utils_Cache::assertValidKey($key); |
febc8b36 SL |
200 | // If we are triggering a deletion of a prevNextCache key in the civicrm_cache tabl |
201 | // Alssure that the relevant prev_next_cache values are also removed. | |
77f080cb | 202 | if ($this->group == CRM_Utils_Cache::cleanKey('CiviCRM Search PrevNextCache')) { |
febc8b36 SL |
203 | Civi::service('prevnext')->deleteItem(NULL, $key); |
204 | } | |
be5eea0d TO |
205 | CRM_Core_DAO::executeQuery("DELETE FROM {$this->table} WHERE {$this->where($key)}"); |
206 | unset($this->valueCache[$key]); | |
207 | unset($this->expiresCache[$key]); | |
eec321a4 | 208 | return TRUE; |
6a488035 TO |
209 | } |
210 | ||
00be9182 | 211 | public function flush() { |
77f080cb | 212 | if ($this->group == CRM_Utils_Cache::cleanKey('CiviCRM Search PrevNextCache') && |
febc8b36 SL |
213 | Civi::service('prevnext') instanceof CRM_Core_PrevNextCache_Sql) { |
214 | // Use the standard PrevNextCache cleanup function here not just delete from civicrm_cache | |
215 | CRM_Core_BAO_PrevNextCache::cleanupCache(); | |
216 | } | |
217 | else { | |
218 | CRM_Core_DAO::executeQuery("DELETE FROM {$this->table} WHERE {$this->where()}"); | |
219 | } | |
be2fb01f CW |
220 | $this->valueCache = []; |
221 | $this->expiresCache = []; | |
124e5288 | 222 | return TRUE; |
6a488035 TO |
223 | } |
224 | ||
c31de879 TO |
225 | public function clear() { |
226 | return $this->flush(); | |
227 | } | |
228 | ||
00be9182 | 229 | public function prefetch() { |
be5eea0d | 230 | $dao = CRM_Core_DAO::executeQuery("SELECT path, data, UNIX_TIMESTAMP(expired_date) AS expires FROM {$this->table} WHERE " . $this->where(NULL)); |
be2fb01f CW |
231 | $this->valueCache = []; |
232 | $this->expiresCache = []; | |
be5eea0d TO |
233 | while ($dao->fetch()) { |
234 | $this->valueCache[$dao->path] = CRM_Core_BAO_Cache::decode($dao->data); | |
235 | $this->expiresCache[$dao->path] = $dao->expires; | |
236 | } | |
be5eea0d TO |
237 | } |
238 | ||
239 | protected function where($path = NULL) { | |
be2fb01f | 240 | $clauses = []; |
be5eea0d TO |
241 | $clauses[] = ('group_name = "' . CRM_Core_DAO::escapeString($this->group) . '"'); |
242 | if ($path) { | |
243 | $clauses[] = ('path = "' . CRM_Core_DAO::escapeString($path) . '"'); | |
244 | } | |
245 | return $clauses ? implode(' AND ', $clauses) : '(1)'; | |
6a488035 | 246 | } |
96025800 | 247 | |
6a488035 | 248 | } |